Go modules en vendoring vormen samen de fundering onder reproducible builds in moderne Go-projecten. Waar modules bepalen welke versies je gebruikt, zorgt vendoring ervoor dat de bijbehorende broncode letterlijk in je repository leeft. Die combinatie geeft je grip op je dependencies, ook als upstream morgen offline gaat of een tag verwijderd wordt.
In dit artikel duiken we dieper in de werking van go.mod, go.sum en de vendor/ map. Je leert wanneer vendoring zinvol is, hoe je het opzet en welke valkuilen je moet vermijden in productieprojecten.
Ben je nog niet bekend met de basis van modules? Lees dan eerst dependency management in Go met modules uitgelegd voor context.
Wat zijn Go modules ook alweer?
Een Go module is een verzameling gerelateerde packages die samen versiebaar en distribueerbaar zijn. De module wordt gedefinieerd door een go.mod bestand in de root van je project. Sinds Go 1.16 is modules de enige ondersteunde manier om dependencies te beheren.
Een typische go.mod ziet er zo uit:
module github.com/example/orders-api
go 1.22
require (
github.com/go-chi/chi/v5 v5.1.0
github.com/jackc/pgx/v5 v5.6.0
github.com/stretchr/testify v1.9.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
)
De eerste require blok bevat directe dependencies. Het tweede blok bevat indirecte dependencies, packages die jouw directe dependencies zelf gebruiken. De // indirect comment wordt automatisch door Go beheerd.
De rol van go.sum
Waar go.mod zegt "ik gebruik deze versies", zegt go.sum "en dit zijn de exacte bytes die erbij horen". Het bestand bevat cryptografische SHA-256 hashes van elke module en zijn go.mod file.
github.com/go-chi/chi/v5 v5.1.0 h1:acVI9kRy...
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:e6W2dK2P...
Bij elke go build of go mod download verifieert Go of de gedownloade modules overeenkomen met deze hashes. Klopt er iets niet? Dan krijg je een hard error. Dit beschermt je tegen compromised registries en onopgemerkte wijzigingen aan bestaande versies.
Commit go.sum altijd mee, ook al voelt het als overbodige ruis in je diffs. Zonder deze file heeft je team geen manier om te verifiëren dat iedereen dezelfde code bouwt.
Wat is vendoring precies?
Vendoring is het proces waarbij je alle gebruikte externe packages kopieert naar een vendor/ map binnen je project. Die map komt vervolgens gewoon mee in je Git-repository.
Het idee is simpel: je project wordt volledig zelfstandig. Geen externe proxy nodig, geen afhankelijkheid van GitHub-beschikbaarheid, en geen verrassingen door disappearing packages zoals het beruchte left-pad incident.
Je activeert vendoring met één commando:
go mod vendor
Dit doet drie dingen:
- Leest je
go.modom te bepalen welke modules nodig zijn - Downloadt ontbrekende modules in de module cache
- Kopieert de daadwerkelijk gebruikte source files naar
vendor/
Merk op dat stap 3 alleen kopieert wat je ook echt importeert. Ongebruikte subpackages blijven weg, wat de vendor map compacter houdt dan je misschien verwacht.
De structuur van de vendor map
Na het draaien van go mod vendor krijg je een structuur die de module-paden spiegelt:
vendor/
├── github.com/
│ ├── go-chi/
│ │ └── chi/
│ │ └── v5/
│ │ ├── chi.go
│ │ ├── mux.go
│ │ └── ...
│ └── jackc/
│ └── pgx/
│ └── v5/
├── golang.org/
│ └── x/
│ └── sys/
└── modules.txt
Het bestand vendor/modules.txt is cruciaal. Het vertelt de Go-toolchain welke modules en versies in de vendor map staan. Dit bestand moet exact overeenkomen met go.mod, anders faalt de build met een mismatch error.
Raak modules.txt nooit handmatig aan. Regenereer altijd met go mod vendor.
Vendoring gedrag vanaf Go 1.14
Sinds Go 1.14 geldt: als een vendor/ map bestaat en je go.mod declareert go 1.14 of hoger, dan gebruikt Go automatisch de vendor map. Je hoeft geen flags door te geven.
Je kunt het gedrag forceren of overschrijven met de -mod flag:
-mod=vendor, gebruik altijd de vendor map (failt als hij niet bestaat)-mod=mod, negeer de vendor map, gebruik de module cache-mod=readonly, gebruik de module cache, maar updatego.modniet
In CI-pipelines is -mod=vendor een veelgebruikte keus voor extra garanties:
go build -mod=vendor ./...
go test -mod=vendor ./...
Wanneer wel en niet vendoren?
Vendoring is geen one-size-fits-all. Hier zijn praktische afwegingen.
Wel vendoren
- Air-gapped builds, je CI-omgeving heeft geen internettoegang
- Strikte compliance, regelgeving vereist dat alle broncode in je eigen repository leeft
- Long-term support, projecten die over 5 jaar nog exact reproduceerbaar moeten zijn
- Legacy dependencies, packages waar upstream instabiel of verdwenen is
Niet vendoren
- Kleine hobbyprojecten, de
go.sumverificatie is vaak genoeg - Libraries, vendoring in een library dwingt je keuzes op aan consumers
- Teams die strak willen updaten, vendoring voegt een extra stap toe aan elk dependency-bump
Voor backends in productie kies ik zelf vaak voor vendoring. Het nadeel, grotere repository, meer diff-ruis, weegt niet op tegen de zekerheid dat een deploy over twee jaar nog steeds werkt. Zie ook onze gids over production deployment van Go apps voor hoe dit aansluit bij release-strategieën.
Workflow: een dependency toevoegen
Een typische workflow met vendoring ziet er zo uit:
# Voeg nieuwe dependency toe
go get github.com/google/[email protected]
# Regenereer vendor map
go mod vendor
# Run tidy om go.mod en go.sum op te schonen
go mod tidy
# Commit alles samen
git add go.mod go.sum vendor/
git commit -m "Add uuid dependency for order IDs"
De volgorde van go mod vendor en go mod tidy maakt uit. tidy verwijdert ongebruikte entries en voegt ontbrekende toe. Draai dit ideaal gezien vóór vendor, zodat de vendor map precies aansluit bij je definitieve go.mod.
Valkuilen die ik in de praktijk zie
Vendor map niet gecommit
Iemand draait go mod vendor lokaal, maar voegt de map toe aan .gitignore. Resultaat: CI faalt omdat -mod=vendor niks vindt. Check of vendor/ in je .gitignore staat en verwijder hem daar.
Inconsistentie tussen go.mod en vendor
Je doet een quick go get maar vergeet go mod vendor te draaien. De build op je laptop werkt (module cache), maar CI breekt (-mod=vendor). Ontdek dit vroeg door in CI een go mod verify stap toe te voegen.
CGO en platform-specifieke files
Sommige packages bevatten .c files of platform-specifieke .go files. Standaard kopieert go mod vendor deze mee, maar ongebruikte platform-files kunnen ruis zijn. Meestal is dit geen probleem, laat de toolchain zijn werk doen.
Tools die buiten de vendor map vallen
De tools.go pattern waarbij je imports gebruikt voor dev-tools (zoals mockgen of stringer) werkt prima met vendoring, mits je de imports daadwerkelijk in een buildable file zet met een build-tag:
//go:build tools
// +build tools
package tools
import (
_ "github.com/golang/mock/mockgen"
)
Zonder deze setup worden tools niet meegevendord en breekt go generate op een verse machine.
Integratie met CI en Docker
Voor Docker-builds met vendoring kun je de hele module-download stap overslaan:
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
COPY vendor/ ./vendor/
COPY . .
RUN go build -mod=vendor -o /app/server ./cmd/server
FROM alpine:3.20
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
Geen go mod download stap, geen proxy-configuratie, geen netwerkdependency tijdens de build. Dit scheelt tijd en maakt je builds deterministisch, ook als GitHub kuchen krijgt.
In GitHub Actions of GitLab CI kun je bovendien de go mod download stap volledig overslaan wanneer de vendor map aanwezig is. De build start direct met compileren.
Voor productie-insights bij deployed apps is logging en observability in Go een goede vervolglezing.
Verifiëren en auditen
Periodiek wil je weten of je vendor map nog klopt met je go.mod. Gebruik hiervoor:
go mod verify
Dit checkt of de cached modules nog overeenkomen met hun hashes. Voor een strengere audit draai je:
go mod vendor
git diff --exit-code vendor/
Als er een diff is, weet je dat iemand go.mod heeft aangeraakt zonder de vendor map bij te werken. Perfect voor een CI-check.
Voor security audits is govulncheck een onmisbare tool. Die scant je dependencies tegen de officiële Go vulnerability database, ook als je vendoring gebruikt.
Vendoring en private repositories
Vendoring wordt extra aantrekkelijk bij private dependencies. Zonder vendor map moet elke ontwikkelaar en CI-runner toegang hebben tot je private Git-servers, inclusief correct geconfigureerde GOPRIVATE en SSH-keys of tokens.
Met vendoring verdwijnt dat hele probleem na de initiële go mod vendor. De source code zit in je hoofdrepository, en nieuwe teamleden of CI-machines hebben geen speciale toegang meer nodig.
Voor meer context over deze setup, check project structuur in Go waar we workspace-organisatie behandelen.
Moderne alternatieven en aanvullingen
Sinds Go 1.21 is er de Go module proxy, die standaard aan staat via GOPROXY=https://proxy.golang.org,direct. Deze proxy cache't publieke modules en biedt semi-permanente beschikbaarheid, ook als een upstream-repo verdwijnt.
Voor veel teams is de proxy plus go.sum een voldoende garantie zonder vendoring. Maar de proxy is nog steeds een extern systeem. Echte onafhankelijkheid bereik je alleen met vendor/ in je repository.
Een middenweg is een eigen proxy draaien, zoals Athens. Die host je zelf en cache't alle modules die je projecten gebruiken. Geen vendor maps, wel volledige controle.
Veelgestelde vragen
Wat is vendoring in Go?
Vendoring is het lokaal opslaan van alle externe dependencies in een vendor map binnen je project. Zo bouwt Go je applicatie zonder de module cache of netwerktoegang te raadplegen, wat reproducible builds en offline compilaties mogelijk maakt.
Wanneer moet ik vendoring gebruiken?
Gebruik vendoring wanneer je reproducible builds wilt garanderen zonder afhankelijk te zijn van externe proxies, bij air-gapped CI-omgevingen, of wanneer compliance-eisen vereisen dat alle broncode in je eigen repository staat. Voor kleinere projecten volstaat de module cache meestal.
Wat is het verschil tussen go.mod en go.sum?
go.mod beschrijft welke modules en versies je project gebruikt. go.sum bevat cryptografische hashes om te verifiëren dat gedownloade modules niet gewijzigd zijn sinds de eerste keer dat ze werden opgehaald, wat de integriteit van je dependencies waarborgt.
Hoe update ik de vendor map?
Voer go mod vendor uit vanuit de root van je project. Dit leest je go.mod, downloadt benodigde modules indien nodig, en kopieert de gebruikte source files naar de vendor map. Commit deze wijzigingen vervolgens mee met je code.
Werkt Go automatisch met de vendor map?
Als de vendor map aanwezig is en je Go-versie 1.14 of hoger is, gebruikt Go deze standaard bij builds. Je kunt dit forceren met de vlag -mod=vendor of uitschakelen met -mod=mod om alsnog de module cache te gebruiken.
Afsluitend
Go modules en vendoring zijn twee lagen van hetzelfde doel: controle over je dependencies. Modules geven je versiebeheer en hash-verificatie, vendoring maakt je project volledig zelfstandig. Voor kritieke productieworkloads is die combinatie goud waard.
Kies bewust: vendoring is geen gratis optie, maar geeft je onafhankelijkheid van het Go-ecosysteem op lange termijn. Voor de volgende stap kijk eens naar testing in Go om te zien hoe je deze stabiele builds koppelt aan een robuuste testsuite. De officiële documentatie op go.dev/ref/mod is tot slot een uitstekende referentie voor dieper graafwerk.