Go CLI のディレクトリ構成(cmd/ と internal/、pkg/ は置かない)

一言まとめ

単一バイナリでも `cmd/<name>/main.go` にして 2 本目の追加コストをゼロにする。実装は既定で `internal/` に置き、公開したい理由があるパッケージだけ外に出す。`pkg/` は意味を持たないディレクトリ階層なので作らない。

作成 · 更新 · #go#project-layout#cli
目次 5 sections
  1. 01 採用した構成
  2. 02 規則
  3. 03 採らなかった構成
  4. 04 関連
  5. 05 更新履歴

調査時点: 2026-04。公開 OSS の mask-pipe(github.com/c12o-dev/mask-pipe)の初期レイアウト決定から。

採用した構成#

mask-pipe/
├── cmd/
│ └── mask-pipe/
│ └── main.go # エントリポイント、フラグ解析、配線
├── patterns/ # 外から見つけてほしいドメインのパッケージ(トップレベル)
│ ├── pattern.go
│ ├── aws_access_key.go # 1 パターン 1 ファイル
│ └── patterns_test.go
├── internal/
│ └── filter/ # 実装の中核
│ ├── filter.go
│ └── filter_test.go
├── go.mod
└── go.sum

規則#

  • 今は 1 バイナリでも cmd/<name>/ を使う。 後からベンチマーク用などの 2 本目を足すときにリファクタが要らない。コストは go build . が go build ./cmd/<name> になるだけ。
  • 既定は internal/。 コンパイラが外部からの import を拒否するので、実装詳細への偶発的な依存を防げる。公開する具体的な理由があるパッケージだけ外に出す。
  • トップレベルに置くパッケージは、仕様書やドキュメントが参照するパスに合わせる。 読んだ人がそこに見つけられることを優先する。ただしトップレベルに置くと API の安定を暗に約束して見えるので、doc.go に「v1 では安定 API ではない」と明記する。後で公開ライブラリとして出すときは import パスを変えられない(変えるならメジャーバージョンアップ)点は織り込んでおく。
  • pkg/ は作らない。 意味を持たないディレクトリ階層が 1 段増えるだけで、Go チームや Dave Cheney も推奨していない。

採らなかった構成#

  • フラット(ルートに main.go と兄弟パッケージ): 最初の数百行までは最も簡単だが、2 本目のバイナリや内部の隔離を始めた瞬間に rename が要る。最終形を最初に選ぶコストはゼロ。
  • cmd/ + pkg/ + internal/(Kubernetes 流): pkg/ の階層に意味がない。
  • ドメインのパッケージも internal/ に入れる: API の約束という点では安全だが、ドキュメントが参照するパスと食い違う。

gh / goreleaser / hugo など評判のよい Go CLI と同じ慣習なので、Go 経験者のオンボーディングコストが下がる。

関連#

更新履歴#

  • 2026-04-16: 初版(mask-pipe ADR-0003)
  • 2026-10-17: Wiki へ移植、概念ページとして再構成

出典

  1. https://github.com/golang-standards/project-layout
  2. https://dave.cheney.net/2014/12/01/five-suggestions-for-setting-up-a-go-project

同じトピック — CLI