---
title: "Goのディレクトリ構成ベストプラクティス｜project-layoutは公式標準ではない理由と最小構成"
url: "https://www.issoh.co.jp/tech/details/4285/"
published: 2024-11-22
updated: 2026-08-12
categories: ["Go"]
publisher: "株式会社一創"
---

# Goのディレクトリ構成ベストプラクティス｜project-layoutは公式標準ではない理由と最小構成

Goのディレクトリ構成を調べると、必ず `golang-standards/project-layout` というGitHubリポジトリに行き当たります。star数は56,443（2026年8月12日時点）。ところが、このリポジトリのREADMEは冒頭で自らを「Goコアチームが定めた公式標準ではない」と否定しています。この記事では、公式ドキュメントが実際に推奨している構成、主要Goリポジトリ15本を実測した採用実態、そして `internal` や `vendor` がGoツールチェーンでどう扱われるかを、Go 1.26.5の実行結果とともに整理します。

## まとめ

Goの公式ドキュメントに「標準ディレクトリ構成」は存在しません。公式が示しているのは [Organizing a Go module](https://go.dev/doc/modules/layout) というページで、そこで意味を持つディレクトリ名は `internal` と `cmd` の2つだけです。`pkg`・`configs`・`deployments` といった名前にツールチェーン上の意味はありません。

実務での結論を先に書きます。新規プロジェクトは `go.mod` と `main.go` の2ファイルから始めてください。非公開にしたいコードが出てきた時点で `internal/` を追加し、バイナリが2本以上になった時点で `cmd/` を追加します。`golang-standards/project-layout` のroot直下19ディレクトリを最初から丸ごとコピーする運用は、README自身が「overkill（過剰）」と書いているとおり避けるべきです。実際、主要Goリポジトリ15本を調べたところ `configs` と `deployments` は1本も採用していませんでした。以下、この判断の根拠を順に示します。

## golang-standards/project-layoutが公式標準ではない3つの根拠

Goのディレクトリ構成を調べる人の多くは、リポジトリ名そのものを検索して「これは何者で、従うべきなのか」を確かめに来ます。答えははっきりしています。従うべき公式規約ではありません。

### README本文が明記する「NOT an official standard」の一文

根拠はリポジトリ自身のREADMEにあります。[Overview節](https://github.com/golang-standards/project-layout)の2段落目は、太字コード表記の次の一文で始まります。

```
This is **`NOT an official standard defined by the core Go dev team`**.
This is a set of common historical and emerging project layout patterns
in the Go ecosystem.
```

同じ段落の後半では、Goコアチームが優れた一般指針を公開しているとして公式ドキュメントの `Organizing a Go module` を名指しで案内しています。続く段落では、学習中・PoC・個人プロジェクトに対して「このレイアウトは overkill であり、`main.go` 1本と `go.mod` があれば十分」と明言し、「クローンして必要なものだけ残し、あとは全部消せ」と結んでいます。リポジトリ自身の主張と「標準だから従う」という読み方は、最初から食い違っています。

### Russ Coxがissue #117で指摘した「pkgは多数派ではない」

2021年4月9日、このリポジトリに[issue #117「this is not a standard Go project layout」](https://github.com/golang-standards/project-layout/issues/117)が立ちました。起票者はGitHubアカウント `rsc`、当時Goプロジェクトのテックリードを務めていたRuss Cox氏です。同issueはコメント255件を集め、2023年8月19日にクローズされました。主張は4点です。

- 公式でないことはREADMEも認めているが、「エコシステムに共通の歴史的・新興のレイアウトパターン集」という自己規定そのものが不正確である
- Goエコシステムの圧倒的多数のパッケージは、importできるパッケージを `pkg` サブディレクトリに置いていない
- ここで説明されている構成は複雑すぎ、実際のGoリポジトリはもっと単純である
- 「golang-standards」を名乗って提示されていることが不幸であり、実際には標準ではない

1点目が最も重い指摘です。「公式ではない」はREADMEも書いていますが、rsc氏はその先を突いて「慣習の集大成でもない」と述べています。起票の動機として「あなたは標準のGoプロジェクトレイアウトを使っていない」と言ってこのリポジトリを貼る人を見かけるようになったから、と本文に書かれています。なお `golang-standards` というGitHub Organizationは2017年8月28日作成・公開リポジトリ2件の独立した組織で、Go本体を管理する `golang` Organizationとは無関係です。

### レイアウトを正面から扱う唯一の公式ページ「Organizing a Go module」

公式ドキュメント側で構成に触れているのは `go.dev/doc/modules/layout` の1ページだけです。ここが示す基本形は、ディレクトリを一切作らないフラット構成です。

```
project-root-directory/
  go.mod
  modname.go
  modname_test.go
```

そこから規模に応じて増やす方向として、同ページが挙げるディレクトリは `internal` と `cmd` の2つだけです。しかも `cmd` は「コマンドとimport可能なパッケージが混在するリポジトリで非常に有用」という条件付きの推奨で、コマンドしかないリポジトリでは必須ではないと明記されています。`pkg`・`configs`・`deployments` はディレクトリ名としては1か所も登場しません。

## 主要Goリポジトリ15本のトップ階層を実測した採用率

「実際のGoプロジェクトはもっと単純」という指摘が今も成り立つのかを、GitHub APIで各デフォルトブランチのルート直下ディレクトリを取得して確かめました。star数上位のGo製OSSから、CLI・サーバー・ライブラリ・インフラ基盤を混ぜて15本を選び、全リポジトリでルートに `go.mod` があることを確認しています。対象は kubernetes/kubernetes、prometheus/prometheus、grafana/grafana、gohugoio/hugo、moby/moby、etcd-io/etcd、hashicorp/terraform、gin-gonic/gin、labstack/echo、caddyserver/caddy、golang/go、docker/cli、containerd/containerd、minio/minio、traefik/traefik、取得日は2026年8月12日です。

### internal 10本・cmd 8本・pkg 7本という採用実態

| ディレクトリ名      | 15本中の採用数 | 公式Goドキュメントでの位置づけ              |
| ------------ | -------- | ----------------------------- |
| docs         | 11       | 言及なし                          |
| internal     | 10       | 公式で推奨・コンパイラが強制                |
| cmd          | 8        | 公式で条件付き推奨                     |
| pkg          | 7        | 言及なし                          |
| scripts      | 6        | 言及なし                          |
| api          | 5        | 言及なし                          |
| vendor       | 4        | go mod vendorの出力先             |
| test         | 3        | 言及なし                          |
| tools        | 3        | 言及なし                          |
| build        | 1        | 言及なし                          |
| examples     | 1        | 言及なし                          |
| third\_party | 1        | 言及なし                          |
| src          | 1        | GOPATHモードの構造（該当はコンパイラのソースツリー） |
| configs      | 0        | 言及なし                          |
| deployments  | 0        | 言及なし                          |

公式ドキュメントが名指しする `internal` と `cmd` が上位に来て、コンパイラ上の意味を持たない `pkg` が7本にとどまる分布です。labstack/echo に至っては、ルート直下のディレクトリが4つだけで `cmd`・`internal`・`pkg` のいずれも持ちません。`src` を持つ唯一の1本は golang/go 本体で、これはGoコンパイラと標準ライブラリのソースツリーであってモジュール形式のアプリケーションではありません。

### configs・deployments・third\_partyが定着しない理由

採用ゼロだった `configs` と `deployments`、1本のみだった `third_party` には共通点があります。いずれもGoツールチェーンが何も解釈しないうえ、置かれる中身がGoコードですらありません。設定ファイルやKubernetesマニフェストの置き場所はGoのレイアウト問題ではなくリポジトリ運用の問題で、実際には `deploy/`、`manifests/`、`charts/` など各プロジェクトに自然な名前が選ばれています。`project-layout` のroot直下19ディレクトリを先に作ると、新しく参加した開発者が「`examples` には何を置く決まりなのか」を毎回確認する手間だけが残ります。使わないディレクトリは作らない。実測から導ける最も実務的な結論です。

## go.modとmain.goから始める最小構成と拡張の分岐点

ここからは実際に手を動かす順序です。Goプロジェクトの出発点は、ディレクトリを1つも作らない状態です。

```
$ mkdir myapp && cd myapp
$ go mod init example.com/myapp
$ cat > main.go
package main

import "fmt"

func main() { fmt.Println("hello") }
```

この2ファイルで `go build` も `go test` も通ります。`go mod init` が生成する `go.mod` に書かれる `go 1.26.5` のようなバージョン行は、単なる記録ではありません。この行がツールチェーンの選択やモジュールの挙動を左右します。バージョン行の意味と自動切り替えの仕組みは [GOTOOLCHAINとは｜Go Toolchainでバージョンを自動切り替えする仕組みと設定方法](/tech/details/5635/)で扱っています。

### 単一パッケージのまま進められる範囲と分割の合図

ファイル数が10本を超えても、それだけでは分割の理由になりません。Goは同一ディレクトリの複数ファイルを1つのパッケージとして扱うため、`auth.go`・`client.go`・`main.go` をルートに並べる構成は公式ドキュメントが「basic command」として提示している形そのものです。

分割すべき合図は、ファイル数ではなく公開範囲です。外部に見せたくない実装が現れたとき、あるいは複数のコマンドから共有したいロジックが現れたときが境界になります。パッケージ境界の設計では、実装を隠しつつ振る舞いだけを公開するインターフェースの使い方が効いてきます。Goの暗黙実装の仕組みは [Goのインターフェースとは？暗黙実装とダックタイピング・型制約を実装例で解説](/tech/details/3920/)にまとめています。

### cmdディレクトリを導入する条件と生成されるバイナリ名

バイナリが1本しかないうちは `cmd/` は不要です。公式ドキュメントも「コマンドのみで構成されるリポジトリでは厳密には必要ない」と書いています。導入する条件は2つに絞れます。バイナリが2本以上あるか、import可能なパッケージとコマンドが同居しているか。どちらかに当てはまったときだけ作ってください。

導入すると、バイナリ名は `cmd` 直下のディレクトリ名がそのまま使われます。Go 1.26.5で `cmd/toolx/main.go` を用意して `go install ./cmd/...` を実行すると、`GOBIN` に `toolx` という実行ファイルが生成されます。ディレクトリ名がユーザーから見えるコマンド名になるため、`cmd/app1` のような仮の名前を残したままにしないでください。ウェブフレームワークを使う場合も同じで、ルーター初期化を `cmd/server/main.go` に置き、ハンドラの実体は別パッケージに逃がす形が扱いやすくなります。Ginでの最小構成は [Gin（Go）とは？v1.12.0の最小構成・本番設定と標準net/httpとの使い分け](/tech/details/4288/)で解説しています。

## internalディレクトリの強制範囲｜Go 1.26.5の実行結果

`internal` は、Goのディレクトリ名の中で唯一コンパイラが挙動を変える名前です。慣習ではなく規則である点が他のすべてのディレクトリと異なります。

### 外部モジュールからのimportが拒否される実際のエラー

規則は [Go 1.4のリリースノート](https://go.dev/doc/go1.4)で導入され、次のように定義されています。「`.../a/b/c/internal/d/e/f` というパッケージは、`.../a/b/c` をルートとするディレクトリツリー内のコードからのみimportできる」。同ページには「Go 1.4ではGo本体のリポジトリにのみ強制され、1.5以降はすべてのリポジトリで強制される」という但し書きも付いています。自分のプロジェクトに効くようになったのはGo 1.5からです。実際にGo 1.26.5でモジュールをまたいでimportすると、ビルドは次のエラーで止まります。

```
$ go build ./...
package example.com/outside
	main.go:6:2: use of internal package example.com/demo/internal/auth not allowed
```

同じモジュール内の `cmd/myapp/main.go` からは問題なくimportでき、外部モジュールからだけ弾かれます。公開範囲の制御をレビューやドキュメントに頼らずに済むということです。公開APIとして維持する意思がないパッケージは、まず `internal/` に置いてください。後から公開範囲を広げるのは容易ですが、一度公開したパッケージを非公開に戻すのは利用者への破壊的変更になります。

### 入れ子internalで公開範囲を階層ごとに絞る設計

`internal` はトップレベルに1つだけという制約もありません。任意の階層に置けて、そのつど親ディレクトリを基準に範囲が決まります。`pkg/util/internal/secret` を用意した状態をGo 1.26.5で確認すると、同じモジュール内であっても `sub/consumer` からのimportは `use of internal package ... not allowed` で拒否され、`pkg/util` からのimportだけが成功します。

モジュール全体には見せたくないが特定サブツリーでは共有したい、という中間的な公開範囲をディレクトリ配置だけで表現できます。マイクロサービスを1リポジトリに同居させる構成では、サービスごとに `services/billing/internal/` を切ることで、隣のサービスから内部実装を直接呼ばれる事故をコンパイル時に止められます。

## pkgディレクトリを作るかどうかの判断基準

`pkg` を作るべきかという問いは、Goのレイアウト論争で最も意見が割れる部分です。判断材料をはっきりさせておきます。

### pkgにコンパイラ上の意味はないという事実

まず前提として、`pkg` という名前にツールチェーン上の効果は一切ありません。Go 1.26.5で `pkg/util` に置いたパッケージを別モジュールからimportすると、何の制約もなくビルドが成功します。`internal` の拒否エラーと対照的に、コンパイラは `pkg` を通常のディレクトリとしてしか見ていません。`project-layout` のREADME自体も `pkg` 節で「非公開パッケージを保証する手段としては `internal` のほうが優れている」「普遍的に受け入れられているわけではなく、Goコミュニティには推奨しない人もいる」と書いています。ただし同じ節は「そのディレクトリのコードが外部から使って安全だと明示的に伝える手段としては依然として有効」とも述べており、全否定はしていません。

この名前が広まった経緯も設計上の必然ではありません。READMEはBrad Fitzpatrick氏のツイートを引いて、初期のGoソースコードがパッケージ置き場に `pkg` を使っていたのをコミュニティが真似した結果だと説明しています。`go help gopath` の出力を見ると、GOPATHモードの `pkg` はコンパイル済みオブジェクト（`pkg/GOOS_GOARCH/foo/bar.a`）の置き場でした。ソースを入れる現在の用法は、元の意味とは別物です。

### pkgが機能する条件とinternalを選ぶべき場面

採用してよい条件は1つだけです。リポジトリのルート直下にGo以外のコンポーネントが大量にあり、Goコードを1か所にまとめないと `go` コマンドの対象範囲が読みにくい場合です。フロントエンドのソース、Terraform定義、Helmチャートが同居するモノレポがこれに当たります。

逆に採用すべきでない場面も明確です。単一バイナリのサーバーアプリケーションで `pkg/` を作るのは過剰です。公式ドキュメントがサーバー系プロジェクトについて「エクスポート用のパッケージを持つことは通常ない」「サーバーのロジックは `internal` に置くことを推奨する」と書いているとおり、外部から使われる予定のないコードを `pkg` に置くと、実装の詳細をどこまで安定させるべきかが曖昧になります。迷ったら `internal` を選んでください。制約が強い側から始めるほうが、後の設計変更が安全になります。

## srcディレクトリを作らない理由とGOPATH時代との違い

「Goでは `src` を作らない」という話は結論としては正しいものの、理由が誤って伝わっていることがあります。ネストが深くなるからではありません。

### go help gopathが示すsrcの本来の役割

`src` はGOPATHモードの構造上の要件でした。`go help gopath` の出力に「`src` ディレクトリはソースコードを保持する。`src` 以下のパスがimportパスまたは実行ファイル名を決定する」とあるとおり、GOPATHに指定したディレクトリは `src`・`pkg`・`bin` という決まった構造を持つ必要があり、コードは `$GOPATH/src/github.com/user/repo` に置く以外の選択肢がありませんでした。importパスはこの物理配置から逆算されていました。

Go Modulesではこの対応関係が `go.mod` の `module` 行に移りました。`module example.com/myapp` と宣言すれば、リポジトリをディスク上のどこに置いてもimportパスは変わりません。`src` はGOPATHという仕組みに紐づいた要件であって、Goの美学ではないわけです。今から `src/` を作ると、GOPATHの構造でもモジュールの構造でもない中途半端な階層が1つ増えるだけになります。

### golang/goリポジトリにsrcがある理由

実測15本のうち唯一 `src` を持つ golang/go は、矛盾ではなく例外です。標準ライブラリのソースが `src/net/http/` に置かれ、この物理配置がそのまま `net/http` というimportパスになっています。アプリケーションモジュールの参考にする対象ではないと切り分けてください。

## vendorディレクトリの自動適用条件と不整合時に出る失敗

`vendor/` は依存パッケージのソースをリポジトリに同梱する仕組みです。`project-layout` のREADMEでさえ「`vendor` パターンですら普遍的ではない」と書いており、実測15本でも採用は kubernetes、moby、docker/cli、containerd の4本にとどまりました。

### go mod vendorが生成するファイルとmodules.txtの役割

手作業でコピーするものではなく `go mod vendor` が生成します。出力は依存パッケージのソースツリーと、`vendor/modules.txt` という目録ファイルの2つです。Go 1.26.5で実行すると `modules.txt` にはモジュールパス・バージョン・`replace` の解決先・`go` ディレクティブのバージョン・vendorへ入れたパッケージ一覧が記録されます。この目録と `go.mod` が一致しているかどうかが、次に述べる判定に使われます。

### go 1.14以降の自動-mod=vendorと不整合エラーの読み方

`-mod=vendor` フラグを明示する必要は、もうありません。`go.mod` の `go` ディレクティブが1.14以上で、かつ `vendor/modules.txt` が存在すれば、`go build` は自動的にvendorディレクトリを参照します。Go 1.26.5で `go list -f '{{.Dir}}'` を叩くと、依存のパスがモジュールキャッシュではなく `vendor/` 配下として返ってきます。

この自動化は、失敗したときのエラーが分かりにくいという副作用を持ちます。vendorの中身が `go.mod` とずれた状態でビルドすると、次のように「モジュールが見つからない」系のメッセージが出ます。

```
cmd/toolx/main.go:6:2: cannot find module providing package example.com/lib: import lookup disabled by -mod=vendor
	(Go version in go.mod is at least 1.14 and vendor directory exists.)
```

ネットワークやプロキシの問題に見えますが、原因はvendorの不整合です。指定した覚えのない `-mod=vendor` が効いていること自体がヒントになっています。依存を追加・更新したら `go mod vendor` を再実行してコミットする、これをCIで検証する。この2点を運用に入れておけば防げます。vendorを使わない場合は `vendor/` をディレクトリごと削除してください。中途半端に残っているのが一番危険な状態です。

## goツールが走査から除外する名前の規則

最後に、レイアウトを決めるうえで知っておくと事故が減る仕様を挙げます。`go` コマンドは特定の名前をパッケージ探索の対象から外します。

### testdata・アンダースコア・ドットが除外される仕組み

`go help packages` の記述はこうです。「ドットまたはアンダースコアで始まるディレクトリ名およびファイル名は go ツールに無視される。`testdata` という名前のディレクトリも同様である」。ディレクトリだけでなくファイル名も対象である点に注意してください。`_helper.go` という名前を付けた時点で、そのファイルはビルドされません。

Go 1.26.5で `testdata/`、`_ignored/`、`.hidden/` のそれぞれに構文エラーを含むGoファイルを置いても、`go build ./...` は正常終了します。`go list ./...` の出力にもこれらは現れません。テスト用のフィクスチャや、ビルド対象にしたくない実験コードの置き場として使える仕様です。`vendor/` も別ルールで除外され、`./...` は `./vendor` 配下のパッケージにマッチしません。

### アンダースコア始まりで静かにスキップされる事故

裏を返せば、通常のパッケージをうっかり `_internal/` のような名前に置くと、ビルドもテストも静かにスキップされます。エラーは出ません。「テストが通っている」のではなく「テストが実行されていない」状態になるため、パッケージのディレクトリ名・ファイル名の先頭にアンダースコアやドットを使わないでください。

## よくある質問

### golang-standards/project-layoutは使ってはいけないのですか？

禁止すべきものではなく、「公式標準として無条件に従うもの」ではないというだけです。README自身がクローンして必要なものだけ残し、残りは削除するよう案内しています。大規模なサーバーアプリケーションで `cmd`・`internal`・`api` だけを採り入れるといった使い方は妥当です。避けるべきなのは、root直下19個のディレクトリを空のまま作って「標準に準拠した」と考える運用です。

### Goのプロジェクトでcmdディレクトリは必須ですか？

必須ではありません。公式ドキュメントは、コマンドだけで構成されるリポジトリでは `cmd` は厳密には不要と書いています。導入の目安はバイナリが2本以上になったとき、またはimport可能なパッケージとコマンドが同じリポジトリに同居しているときです。バイナリ1本のツールなら、ルートに `main.go` を置く構成で問題ありません。

### internalとpkgはどちらを使うべきですか？

迷ったら `internal` です。`internal` は外部モジュールからのimportをコンパイラが拒否しますが、`pkg` という名前にツールチェーン上の効果はありません。公式ドキュメントもサーバー系プロジェクトのロジックは `internal` に置くよう推奨しています。`pkg` が有効なのは、Go以外のコンポーネントが多いリポジトリでGoコードを1か所にまとめたい場合に限られます。

### Goでsrcディレクトリを作らないのはなぜですか？

`src` はGOPATHモードでimportパスを決めるための構造上の要件で、Go Modulesではその役割が `go.mod` の `module` 行に移ったためです。`go help gopath` には今も「`src` 以下のパスがimportパスまたは実行ファイル名を決定する」と記載されています。モジュール環境で `src/` を作っても、importパスには影響せず階層が1つ深くなるだけです。

### vendorディレクトリはコミットすべきですか？

アプリケーションでは、ビルドの再現性やネットワーク制限のある環境が要件なら有効です。ライブラリではコミットすべきではありません。採用する場合は `go mod vendor` の実行結果をそのままコミットし、依存を更新するたびに再生成してください。`go.mod` の `go` ディレクティブが1.14以上かつ `vendor/modules.txt` があれば、ビルドは自動的にvendorを参照します。

## 関連記事

- [GOTOOLCHAINとは｜Go Toolchainでバージョンを自動切り替えする仕組みと設定方法](/tech/details/5635/)
- [Goのインターフェースとは？暗黙実装とダックタイピング・型制約を実装例で解説](/tech/details/3920/)
- [Gin（Go）とは？v1.12.0の最小構成・本番設定と標準net/httpとの使い分け](/tech/details/4288/)
- [Go Echoフレームワーク実践ガイド｜特徴・Gin比較とベストプラクティス](/tech/details/3399/)
- [Go 1.27の新機能・変更点まとめ｜最新バージョンの確認方法と移行ガイド](/tech/details/10416/)

---

出典: [Goのディレクトリ構成ベストプラクティス｜project-layoutは公式標準ではない理由と最小構成](<https://www.issoh.co.jp/tech/details/4285/>)（株式会社一創）
