TypeORMでDBコンテナに接続する際はホスト名はlocalhost
はじめに
同一ネットワーク内のdockerコンテナに接続する際のホスト名は接続先コンテナのサービス名で良いと思いますが、TypeORMのCLIツールを使用しdockerコンテナに接続する際は、たとえ接続先と同一ネットワーク内のdockerコンテナ内に接続情報が記述してあってもホスト名はホストOS(localhost)になるという話です。個人開発中、そこで躓いたので備忘録も兼ねてまとめておきます。
やりたかったこと
docker-composeでDBコンテナとAPIコンテナをたて、APIコンテナからDBコンテナに接続していました。
その後、TypeORMのCLIツールでAPIのプロジェクト内にマイグレーションファイルを生成しようとしてこちらを参考にAPIのプロジェクト内のsrc/config/ormconfigにDataSourceを定義し以下のCLIコマンドを実行すると、
$ npm run typeorm -- migration:generate migration/InitialMigrations -d src/config/ormconfig.ts
以下のエラー。
> backend@0.0.1 typeorm
> typeorm-ts-node-commonjs migration:generate migration/InitialMigrations -d src/config/ormconfig.ts
Error during migration generation:
Error: getaddrinfo ENOTFOUND db
at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:120:26) {
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'db'
}
'db'というのが接続先コンテナのサービス名で、ホスト名に指定していましたが、ホスト名が名前解決できませんでした。
同一ネットワーク内のdockerコンテナに接続する際のホスト名は接続先コンテナのサービス名でいいのではないのか😡EntityでDB接続情報の定義をしていた際はホスト名が接続先コンテナのサービス名でDB接続できていたよね😡
となりました。
使用したツールなど
- docker
- TypeORM
試したこと
マイグレーションファイルが生成できなかったため以下を試しました。
2つのコンテナが同じネットワークに所属しているか確かめた
特にネットワークの設定はせず、同じdocker-composeファイルで2つのコンテナを定義していたので、デフォルトで同じネットワークに所属しているはずですがそれを確かめました。
$ docker network ls
でネットワークの一覧が見られます。今回はデフォルトなので「アプリ名_default」があることを確認しました。そのNETWORK IDをもとに
$ docker network inspect <NETWORK ID>
で詳細を確認し、確かにDBコンテナとAPIコンテナが同一ネットワーク内にあることがわかりました。
DBコンテナに接続できているか確かめた
EntityでDB定義をしていた際はEntityを編集することでDBを書き換えられていたので、ホスト名が接続先コンテナのサービス名でDB接続できているはずですが、一応確認しました。
まずDBコンテナに入り、IPアドレスを確認します。
# コンテナに入る
$ docker exec -it <コンテナ名> bash
# IPアドレスを確認
hostname -i
次にAPIコンテナに入りpingを打ってみます。
# コンテナに入る
$ docker exec -it <コンテナ名> bash
# パッケージをアップデートする
$ apt-get update
# pingをインストールする
$ apt-get install iputils-ping net-tools
結果pingは通り、APIコンテナからDBコンテナには接続はできていることがわかりました。
ホスト名を色々変えて試してみる
APIコンテナからDBコンテナに接続できているのに同じホスト名で接続できないとはどういうことなのか全くわからず…ひたすら調べつつホスト名をそれっぽいものにして色々試しました。
まず、'127.0.0.1'はlocalhostを指すIPアドレスであることがわかったため、試してみました。docker内でlocalhostは自分自身、今回で言うとAPIコンテナを指すのでとこれではできないと思ったのですが、試しにホスト名を'127.0.0.1'にしてみると、名前解決できマイグレーションファイルが生成されました!なぜ!?!?
次にホスト名を'localhost'にしてみます。できました。
'localhost'でマイグレーションファイルが生成できることはわかったのですが、自分自身にアクセスしたはずがDBコンテナに接続できるとはどういうことなのでしょうか。
'host.docker.internal'という特別なDNS名も試しました。これはdockerコンテナから見てホストOSを指す特別なDNS名ですので、DBコンテナに接続したい私は違うかなと思ったのですが、EntityでDB接続情報の定義をしていた際には、ホスト名を'host.docker.internal'にして接続できていたので試しました。
結果、'host.docker.internal'も名前解決できませんでした。
考えたこと
そもそもEntityでDB接続情報の定義をしていた際には、ホスト名を'host.docker.internal'にして接続できていた件ですが、これはおそらく、接続先コンテナのサービス名にするべきですがホストOSに接続したことでホストOS経由でDBコンテナに接続できていたと考えられます。
もう一度マイグレーションファイル生成コマンドを見返してみると、dockerコマンドではなくTypeORMのCLIツールを使って実行している…ではもしかしてAPIコンテナが立っていなくても実行できるのかもしれないと考えました。
解決
DBコンテナのみを立ててCLIコマンドを実行すると…動きました。
ということは、マイグレーションファイル生成コマンドはローカル(ホストOS)で動いており、APIコンテナとは関係がなさそうです。
だから、接続先コンテナのサービス名ではアクセスできなかったのですね。
そして、先ほど試した'localhost'と'127.0.0.1'はAPIコンテナではなく、ホストOS自身を指していたことになります。よって、'localhost(ホストOSのこと、自分自身):ポート番号'に接続することで、ホストOSの指定されたポート番号にマッピング(配置)されているdbコンテナに接続できたと言えると思います。
まとめ
TypeORMのCLIコマンドはコンテナ内ではなくホストOSで動いていたため、ホスト名は'localhost'または'127.0.0.1'でDBコンテナに接続することができる。
イメージ図↓
感想
よく考えたらTypeORMのCLIコマンドがコンテナ内ではなくホストOSで動くのは当たり前ですね。言われてみればそうなのですが、別でコンテナ同士のDB接続情報を書いていたので、同じ値になるはずだと思ってしまいました。解決してよかったです🥰
相談に乗ってくださった方々、公式ドキュメント、技術ブログなどに感謝です。
Discussion