インターン先でvendorを使うことがあり、go mod tidyとは何が違うのかわからなかったため、vendorとはどんな仕組みなのか調べてみました。
vendorとは
vendorとは、goの依存パッケージをローカルのプロジェクト内に保存する仕組みです。
go mod vendorと打つことでプロジェクト内に.vendorディレクトリが生成され、そこにプロジェクト内で依存しているパッケージをダウンロードしてくれます。
goにおけるパッケージのダウンロードは、go getやgo mod tidyを使用して、$GOPATH/pkg/modにダウんロードすることになります。
このパスはローカルキャッシュとして、他プロジェクトにも同じように使い回していくことができます。
vendorでは、他プロジェクトに使い回すことができず、.vendorディレクトリがあるファイル配下のみでパッケージが機能します。
vendorのメリット
vendorを行うことでプロジェクトが依存しているモジュールの依存関係固定化を行ってくれます。
ただ、go.sumでversion等は固定されているので、vendorを知らない方は依存関係固定化なんていらなくないか?と思うことがあると思います。
go mod tidy等でダウンロードしたモジュールは$GOPATH/pkg/modにダウンロードされますが、全プロジェクトのパッケージをキャッシュしてしまっているので、プロジェクトに依存しているパッケージだけ抽出してみるということが大分難しいです。
vendorを使うと上のような問題が解決するのですが、大分想像しづらいと思うので、もう少し具体的な例を出してみようと思います。
1.オフライン環境下におけるビルド
オフライン環境でビルドしないといけず、ビルド環境に該当パッケージがダウンロードされていないとビルドを行うことができません。
また、モジュールのversionが上がると適宜ビルド環境で特定のモジュールを入れ替えるという作業が必要になります。
そのためvendorディレクトリを作成して、ビルド環境に持っていくことで、依存するパッケージのみが全てビルド環境に入っている状況にすることができます。
2.ビルド時間の短縮
上に大分関係していますが、vendorディレクトリにモジュールが入っていることによって、モジュールのダウンロードが不要になるため、結果ビルドする際にかかる時間が少なくなります。
3.privateなパッケージの再利用
GitHubのプライベートリポジトリで管理されているGoのパッケージをインストールするには、そのリポジトリへのアクセス権が必要です。ローカルでの開発では自身のGitアカウントを使用できますが、Gitアカウントが紐付いていない環境、例えばGitHub ActionsのようなCI/CD環境でGoモジュールをダウンロードする必要がある場合はどうでしょうか?
GitHub Actionsで自動ビルドやテストを行う際、ホステッドランナー上でGoモジュールをダウンロードする必要があります。このダウンロードには、プライベートリポジトリからクローンするためのPersonal Access Token (PAT) が必要となり、そのPATをGitHub Actionsで設定したり、DockerイメージでのビルドではDockerfileに渡したりする必要があります。
しかし、PATが外部に漏洩すると、そのプライベートリポジトリが悪用される重大なリスクがあります。 このセキュリティリスクを軽減するため、GitHub Actionsでは一時的にPATを発行する actions/create-github-app-token などのActionが提供されています。
このActionを通して発行されたPATを使用してプライベートなモジュールをクローンした後、vendorディレクトリを利用することで、Goモジュールの管理とデプロイをより効率的に行えます。
vendorディレクトリには依存関係にあるモジュールのコピーが保存されるため、Dockerイメージをビルドする際に、自身のストレージに入っているGoモジュールキャッシュ全体をコピーする代わりにvendorディレクトリのみをコピーするだけで済みます。これにより、Dockerfileの記述が簡単にされ、最終的なイメージサイズも小さくなります。
vendorを実際に使ってみる
今回は上の話を踏まえてvendorを実際に使ってみようと思います。
vendorディレクトリを生成
vendorディレクトリを生成してみます。
go mod vendor
こうすることでvendorディレクトリが生成されます。
❯ ls
go.mod go.sum main.go vendor
vendorディレクトリの中を見てみると下のようになっていました。
❯ tree
.
├── github.com
│ └── maooz4426
│ └── hello
│ └── hello.go
└── modules.txt
actionsでvendorのメリットを実感してみる
vendorのメリットの方で書いたprivateなパッケージの再利用を実感するためにactionsでvendorを使ったビルドを行ってみようと思います。
まずgo mod vendorを実行してみる
privateなパッケージをgo.modに依存関係として追加するために下のように環境変数を追加します。
❯ export GOPRIVATE="github.com/maooz4426/hello"
これを行うことでダウンロードできます。
go mod tidy
go.modに依存関係を追加したのでpushします。
まず失敗する例をworkflowで構築してみます。
name: build
on:
pull_request:
types: [opened, synchronize, reopened]
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5
with:
go-version-file: "go.mod"
- name: Install dependencies
run: go mod vendor
- name: go build
run: go build -mod=vendor -v ./...
Actionsにはprivateリポジトリの閲覧権限が存在しないため怒られました。
Run go mod download
go mod download
shell: /usr/bin/bash -e {0}
go: github.com/maooz4426/hello@v1.0.2: reading github.com/maooz4426/hello/go.mod at revision v1.0.2: git ls-remote -q origin in /home/runner/go/pkg/mod/cache/vcs/18842ccf53f7f60310496984429b6bd1a1db7a42f058f8dba917eb73175a7732: exit status 128:
fatal: could not read Username for 'https://github.com': terminal prompts disabled
Confirm the import path was entered correctly.
If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.
Github Appの準備
上のビルドを成功させるためにGithubAppを準備します。
Developers settingsからGithubAppを作成します。

Webhookはオンになっていますが、最初はいらないのでチェックは外します。

上でAppを作成した後下のように作成したAppを該当リポジトリにインストールします。

repositoryの設定
Github AppのIDをvariablesとして登録します。
catコマンドで秘密鍵が下の形式で表示されるのでそれをコピーします。
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----
ビルドが成功するようにworkflowを組む
下のようにworkflowsを組んで見ました。
privateリポジトリを見るためtokenを発行するactionを使うことでパッケージのインストールを可能にしています。
name: build
on:
pull_request:
types: [opened, synchronize, reopened]
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e #v2.0.6
id: generate-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- name: Configure Git for private modules
run: |
git config --global url."https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com".insteadOf "https://github.com"
env:
GOPRIVATE: github.com/maooz4426/*
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5
with:
go-version-file: "go.mod"
- name: Install dependencies
run: go mod vendor
- name: go build
run: go build -mod=vendor -v ./...
上のように組んでみるとビルドが成功します。
vendorをDockerfileに渡してみる
実際にDockerfileにvendorをコピーさせてみます。
FROM public.ecr.aws/docker/library/golang:1.24.2
WORKDIR /app
COPY go.mod go.sum ./
COPY vendor/ ./vendor/
COPY *.go ./
RUN go build -mod=vendor -o main .
workflowsは下のように修正してみました。
name: build
on:
pull_request:
types: [opened, synchronize, reopened]
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e #v2.0.6
id: generate-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- name: Configure Git for private modules
run: |
git config --global url."https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com".insteadOf "https://github.com"
env:
GOPRIVATE: github.com/maooz4426/*
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5
with:
go-version-file: "go.mod"
- name: Install dependencies
run: go mod vendor
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb
- name: Build and push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
with:
context: .
push: false
tags: |
myapp:latest
myapp:pr-${{ github.event.number }}
cache-from: type=gha
cache-to: type=gha,mode=max
先述しましたが、もしvendorを使っていなかった場合は、Dockerfileでgo mod tidyをする必要があ離ますが、その場合は閲覧権限のあるPATを渡さなければなりません。またGOPATH配下のモジュールキャッシュを使うこともできますが、GOPATHを指定するとイメージサイズが大きくなりますし、特定のパッケージだけコピーするように記述するのも大分めんどくさいため制限があります。
このような問題をvendorを使うことで解決できます。
まとめ
vendorを使用することで安全にprivateリポジトリからのgoパッケージのクローン、そこからactions等でのビルドが行えるようになります。
個人開発ではあまり出会うことは少ないと思いますが、vendorを使う機会があったら一度調べてみると面白いと思います。









