Dockerのコンテナの強みの1つは環境を簡単に複製でき、使い捨てられる点にあります。しかし、データベースのデータのように「使い捨てられては困る情報」があったり、データベースからデータを取り出して別のサービスで使用するために、複数のコンテナを組みあわせてデータを通信する必要もあります。本記事では、名前付きボリュームによるデータの永続化とコンテナ間通信の仕組みについて解説します。
目次
- コンテナの使い捨てと永続化の必要性
- ストレージの取り扱い
- ボリュームを操作する基本コマンド
- 【実践】DBのデータを永続化してみる
- コンテナ間の通信
- ネットワークを操作する基本コマンド
- 【実践】Adminerを使ったDB接続と名前解決の検証
- まとめ
コンテナの使い捨てと永続化の必要性
Dockerコンテナは、そのライフサイクルが終われば(削除されれば)、コンテナ内部で行われた変更や作成されたファイルもすべて消滅するという「ステートレス(状態を持たない)」な性質を持っています。このステートレスな性質を持っているため、サービスを展開したときに、負荷の大きさに応じてサーバーを増やしたり、減らしたりするのが容易になり、スケーラビリティが高くなります。
これはアプリケーションの実行環境としては非常に強力ですが、データベースのように「データを蓄積すること」が目的のコンテナにおいては致命的な問題になります。コンテナをアップデートするために古いコンテナを削除した瞬間、データや投稿内容が全て消えてしまうためです。
この問題を解決するのが、Dockerが提供するボリュームという仕組みです。
ストレージの取り扱い
Dockerでストレージを扱うには、大きく分けて「バインドマウント」と「名前付きボリューム」の2つの手法があります。
バインドマウント(Bind Mount)
ホストマシン(自分のPC)上の特定のフォルダやファイルを、コンテナ内のパスに直接紐づける(マウントする)手法です。自分のデスクトップなどにあるフォルダをコンテナの中のフォルダに紐づけて、コンテナからホストマシンのデータにアクセスできるようにします。
バインドマウントは即時反映とツール連携がしやすいという特徴があります。ホスト側でファイルを書き換えると、即座にコンテナ内に反映される特徴があり、ホスト側のエディタ(VS Codeなど)からファイルを直接編集・管理するのに適しています。コンテナを再ビルドせずに動作確認ができるため、開発のテンポがよくなります。このため、開発中のソースコードの同期などに使用します。
コマンドはdocker runを実行するときに、-v [ホストの絶対パス]:[コンテナ内のパス]の形式で指定します。
例えば、ホストマシンのカレントディレクトリのsrcフォルダをコンテナmy_appの/app/srcフォルダにマウントする場合は以下になります。
docker run -v $(pwd)/src:/app/src my-app名前付きボリューム(Named Volume)
Dockerエンジンが管理する専用のストレージ領域に名前を付けて管理する手法です。データの具体的な保存場所(ホスト上のパス)はDockerが管理するため、ユーザーはパスを意識せずボリュームの名前だけでデータを呼び出せます。
名前付きボリュームは管理が容易でパフォーマンスがよく、設定を使いまわしやすいという特徴があります。具体的には、docker volume lsやdocker volume rmといったコマンドでDocker側から一元管理が可能です。また、特にWindowsやmacOSでDocker Desktopを使用している場合、ホストのファイルシステムを経由しないため、バインドマウントよりもIO速度が高速になる傾向があります。また、ホスト側のディレクトリ構造に依存しないため、異なるOSでも同じ設定を使いまわせます。この名前付きボリュームは主に、データベースのデータ保存などに使用します。
コマンドはdocker runを実行するときに、-v [ボリューム名]:[コンテナ内のパス]の形式で指定します。
例えば、my-db-dataという名前のボリュームをコンテナpostgresの/var/lib/postgresql/dataにマウントする場合は以下になります。
docker run -v my-db-data:/var/lib/postgresql/data postgresコマンドを打つ前にあらかじめ後述の方法でボリューム(my-db-data)を作成しておく必要があります。
名前付きボリュームとバインドマウントの使い分け
名前付きボリュームとバインドマウントの使い分けですが、データベースのデータファイル、ログファイル、ライブラリのキャッシュなど「自分が触らないデータ」は名前付きボリュームを使用して、Dockerに管理を任せるのがよいです。一方で、プログラムのソースコードや設定ファイル、静的コンテンツ(HTML/CSS)などは、手元のエディタから触れるバインドマウントを使うと、変更して結果を確認するプロセスを速く回すことができます。以下に、それぞれの特徴をまとめます。
| 特徴 | 名前付きボリューム (Named Volume) | バインドマウント (Bind Mount) |
|---|---|---|
| データの管理元 | Dockerエンジン | ユーザー(ホストPCのパス) |
| 主な用途 | 永続化が必要なデータ(データベースなど) | 頻繁に編集するファイル(ソースコードなど) |
| ホストからの視認性 | Docker内部に隠蔽される | 通常のフォルダとして見える |
| OS依存性 | 低い(Dockerが差異を吸収) | 高い(パスの書き方がOSで異なる) |
ボリュームを操作する基本コマンド
名前付きボリュームは、コンテナとは独立したリソースとして管理されます。基本的なコマンドを以下に示します。
ボリュームの作成
データを保存する場所を作成します。
docker volume create <ボリューム名>docker run実行時に自動で作成させることも可能ですが、本番運用やチーム開発では、どの名前のボリュームを使うかを明確にするため、あらかじめ作成しておくのが定石です。
ボリュームの一覧確認
現在、自分のPC内にどのようなボリュームが存在するかを確認します。
docker volume lsコンテナを削除したのにディスク容量が減らないといった場合、古いボリュームが残り続けている可能性があります。その場合はこのコマンドで確認できます。
コンテナへの割り当て
作成したボリュームをコンテナにつなぎます。docker runコマンドのオプションとして記述します。
docker run -v <ボリューム名>:<コンテナ内のパス> <イメージ名>コンテナ内のパスは、使用するイメージ(PostgreSQLやMySQLなど)ごとに決まっています。公式ドキュメント(Docker Hubなど)を確認することで、データがどこに保存される仕様かを把握します。
ボリュームの削除
不要になったデータを完全に削除します。
docker volume rm <ボリューム名>対象のボリュームを使用しているコンテナ(停止中も含む)が存在する場合、エラーが出て削除できません。先にdocker rmでコンテナを削除してから実行する必要があります。
一度削除するとデータは二度と復元できません。本番環境や共有環境では、実行前に必ず対象のボリューム名に間違いがないかを再確認しましょう。
【補足】docker volume createコマンドによる高度なボリューム管理--driver (または -d):ボリュームドライバー名を指定します。ボリュームドライバーは、コンテナの永続データをどこに、どのように保存するかを定義する仕組みです。デフォルトはlocal(自分のPC内に保存)ですが、クラウドステレージやネットワーク共有ストレージへの保存も可能にできます。--opt (または -o):ドライバー固有のオプション(例えば、マウント時の書き込み権限など)を指定します。--label:ボリュームに「タグ」のような管理用メタデータを付与します。多数のプロジェクトが混在するサーバーでは、「どのプロジェクトのボリュームか」をラベルで識別し、一括削除などの自動化処理に役立てます。
【実践】DBのデータを永続化してみる
PostgreSQLに対して名前付きボリュームを使用してみます。まず、ボリュームの作成を行います。
docker volume create my-project-db-data次に、作成したdb-dataをマウントして、データベースのコンテナを起動します。
docker run -d --name my-postgres -e POSTGRES_PASSWORD=password -v my-project-db-data:/var/lib/postgresql postgres:18.3-alpine3.23ここで、-v <ボリューム名> : <コンテナ内のパス>を使って、名前付きボリュームをマウントしている。コンテナ内のパス/var/lib/postgresqlは、DockerHubで公開されているPostgreSQL(Docker Official Image版)のドキュメントに記載されているパスです。ほかのデータベース(MySQLなど)を使う場合も、必ずDocker Hubの公式ドキュメントでパスを確認しましょう。また、-eは環境変数を定義するオプションです。
コンテナを起動した後は、コンテナ内に入り、テスト用のテーブルとレコードを作成します。
docker exec -it my-postgres psql -U postgres -c "CREATE TABLE test (id serial PRIMARY KEY, val text); INSERT INTO test (val) VALUES ('Hello Docker Volume');"docker exec -it <コンテナ名> <コンテナ内で実行したいコマンド>は実行中にコンテナに対して、外部から新しいコマンドを実行させる命令です。コンテナmy-postgresの中でpsql以降のコマンドを実行しています。オプション-iでコンテナ側の標準入力を開き続け、これにより入力をコンテナに送ることができるようになります。また、オプション-tで仮想端末を割り当てます。これがないと、出力の結果に色がつかなかったり、文字化けしたり、プロンプトが表示されなかったりします。慣習として-itをセットで書くことがほとんどです。
データベースにテストデータを追加した後は、データが永続化されていることを確認するために、あえてコンテナを完全に削除します。
docker rm -f my-postgresこの時点でコンテナは消滅しましたが、ボリュームmy-project-db-dataはDocker内に残っています。再度、同じボリュームを指定して新しいコンテナを起動します。
docker run -d --name my-new-postgres -e POSTGRES_PASSWORD=password -v my-project-db-data:/var/lib/postgresql postgres:18.3-alpine3.23データが残っているかを確認してみます。
docker exec -it my-new-postgres psql -U postgres -c "SELECT * FROM test;"「Hello Docker Volume」という結果が表示されれば成功です。
id | val
----+---------------------
1 | Hello Docker Volume
(1 row)新しくコンテナを作り直しても、永続化されたボリュームからデータを読み込めることを確認できました。
テストの後始末をします。ボリューム一覧を表示してみます。
docker volume ls
DRIVER VOLUME NAME
local my-project-db-data先にコンテナを削除して、それからボリュームを削除します。
docker rm -f my-new-postgres
docker volume rm my-project-db-dataコンテナ間の通信
複数のコンテナを連携させる場合、例えば、Webアプリのコンテナとデータベースコンテナをつなぐ場合には、どうやってデータベースコンテナの場所(IPアドレス)を特定するかという問題があります。コンテナのIPアドレスは起動のたびに変わる可能性があるため、固定のIPアドレスを指定するのは現実的ではありません。そこで利用するのがユーザー定義ネットワークです。
Dockerでは、名前解決(DNS)ができる仕組みが備わっており、共通のネットワークに参加しているコンテナ同士であれば、「コンテナ名」をホスト名として指定するだけで通信が可能になります。手順は以下になります。
- 専用のネットワークを作成します。
- 各コンテナをそのネットワークに参加させて起動します。
- Webアプリ側の設定ファイルには、DBの接続先として
localhostではなく、DBのコンテナ名を記述します。
これにより、IPアドレスを意識することなく、コンテナ間を連携させることが可能になります。
ネットワークを操作する基本コマンド
コンテナ間通信を支えるユーザー定義ネットワークを管理するための主要なコマンドを以下に示します。
ネットワークの作成
コンテナ同士が所属するためのユーザー定義ネットワークを作成します。
docker network create <ネットワーク名>Dockerにはデフォルトのネットワークも存在しますが、コンテナ名による名前解決(DNS)ができるのは、このコマンドで作成した「ユーザー定義ネットワーク」だけという仕様があります。また、ユーザー定義ネットワークは、自分で作ったネットワークに参加しているコンテナ同士だけが通信できるようにすることで、関係ないコンテナからのアクセスを遮断し、セキュリティを高めることができます。そのため、通常はデフォルトネットワークではなく、ネットワークを明示的に作成します。
ネットワークの一覧確認
現在、どのようなネットワークが存在するかを確認します。
docker network lsbridge、host、noneなどが初めから表示され、これらは標準ネットワークと呼ばれます。通常、実務においては、これらをそのまま使うことはほとんどありません。
コンテナを起動する際、何も指定しないとデフォルトのbridgeネットワークに接続されます。このネットワークではコンテナ名による名前解決(DNS)が機能しないため、コンテナ同士を名前で呼び合って通信するには、「ユーザー定義のネットワーク」を新しく作成する必要があります。
hostはコンテナがホストのIPアドレスを直接使い、ポートもホスト上でそのまま公開されます。コンテナとホストの間のネットワークの分離(隔離)が行われないため、特定のパフォーマンス要件がある場合を除き、通常は使いません。
noneはネットワーク接続を一切持たない状態です。外部との通信も、コンテナ同士の通信もできない完全に隔離された環境が必要な場合(計算処理のみを行うなど)に使われます。
コンテナのネットワークへの参加
コンテナを起動する際、特定のネットワークに所属させます。
docker run --network <ネットワーク名> <イメージ名>共通の<ネットワーク名>を指定して起動したコンテナ同士だけが、お互いの名前で通信できるようになります。
ネットワークの削除
不要になったネットワークを削除します。
docker network rm <ネットワーク名>ボリュームと同様、そのネットワークを現在使用しているコンテナがある場合は削除できません。先にコンテナを停止・削除してから実行します。
【補足】ネットワーク設定をカスタマイズするdocker network createの主要なオプション--driver(または-d):ネットワークの種類(通信方式)を選択します。代表的な値としてbridge(デフォルト), host, overlayを与えます。「複数のサーバー間での通信」が必要になった際、ここをoverlayに変更します。--subnet:ネットワークのIPアドレスの割り当て範囲を明示的に指定します。例えば、--subnet=192.168.10.0/24のように与えます。この場合、24bitが固定(1ブロック8bitなので192.168.10.が固定)なので256個の範囲になります。会社のネットワーク資産とIPアドレスが衝突するのを防ぐために、あらかじめ決まった範囲を割り当てることがあります。--internal:外部への出口をふさぎます。このネットワークに参加しているコンテナを、インターネット(外部)から完全に隔離します。データベースコンテナなど、インターネットにつなぐ必要が一切なく、アプリコンテナからのみアクセスされれば良い秘匿性の高い環境を作る際に使用します。セキュリティを高めるために有効です。
【実践】Adminerを使ったDB接続と名前解決の検証
データベース管理ツールadminerを例に、データベースと連携させるネットワークとボリュームを構築します。
まず、コンテナ同士がコンテナ名で呼び合えるための共通ネットワークを作成します。
docker network create db-net次に、データを保存する名前付きボリュームを作成します。
docker volume create my-db-dataこのコマンドを打たなくても、コンテナ起動時に存在しないボリュームを-vで指定すれば自動的に作成されますので、省略することも可能です。
ネットワークとボリュームを準備したら、データの保存先にするデータベースコンテナを起動します。第18世代のPostgreSQLを使用し、ボリュームとネットワークを同時に指定します。
docker run -d --name db-server --network db-net -v my-db-data:/var/lib/postgresql -e POSTGRES_PASSWORD=password postgres:18.3-alpine3.23-dはバックグラウンドでの実行、--nameはコンテナの名前(db-server)を指定します。--networkオプションで参加するネットワークを指定し、-vオプションで名前付きボリュームをコンテナ内のフォルダに紐づけています。コンテナ側のパス/var/lib/postgresqlは、DockerHubで公開されているPostgreSQL(Docker Official Image版)のドキュメントに記載されているパスです。
次に、データベース管理ツールを起動し、データベースと同じネットワークに参加させます。
docker run -d --name db-client --network db-net -p 8080:8080 adminerコンテナ名により名前解決できているかを確認します。ブラウザでhttp://localhost:8080を開きます。以下の情報を入力してログインします。
| 項目 | 入力値 | 指定場所(入力値根拠) |
|---|---|---|
| System | PostgreSQL | 使用しているDockerイメージ(postgres)の種類に基づきます。Adminerは複数のDBに対応しているため、接続先エンジンの種類を選択します。 |
| Server | db-server | データベースコンテナの起動時オプション--name db-serverで設定したデータベースコンテナの名前です。Dockerネットワーク内では、この名前がホスト名として解決されます。 |
| Username | postgres | 公式postgresイメージのデフォルト設定です。環境変数-e POSTGRES_USERを指定しない場合、自動的にこの名前になります。 |
| Password | password | データベースコンテナの起動時オプション -e POSTGRES_PASSWORD=passwordで指定した文字列です。 |
ここで、ログインが成功できることを確認できたら、データベース管理ツールのコンテナ(db-client)が、Docker内部のDNSサーバーを介してdb-serverというホスト名をIPアドレスに解決できたことを示しています。

次に、データの永続化を検証します。まず、Adminerにログインした状態において、画面左上の「データベースを作成」をクリックし、名前(例:test_db)を入力して保存します。次に、作成したDBを選択し、「テーブルを作成」から適当なテーブル(例:users)を作成し、1件以上のデータを保存します。
テスト用のデータベースをボリュームに保存したので、実行中のデータベースコンテナを強制削除します。
docker rm -f db-serverdocker psコマンドによりdb-server が一覧から消えていることを確認します。確認できたらデータベースプロセスは完全に消滅しています。新しいデータベースコンテナを同じボリュームと同じネットワークを用いて起動します。
docker run -d --name db-server --network db-net --mount source=my-db-data,target=/var/lib/postgresql/ -e POSTGRES_PASSWORD=password postgres:18.3-alpine3.23--mount source=db-data,target=/var/lib/postgresql/ を指定することで、新しいコンテナは以前と同じ物理領域(db-data ボリューム)をマウントして立ち上がります。これまで通り-v db-data:/var/lib/postgresql/を使用しても問題ありません。違いは--mountを使った場合、ボリュームが存在しなければエラーになりますが、-vを使うとボリュームが存在しない場合に自動的に作られるということです。
ブラウザでhttp://localhost:8080を開き、データベース管理ツールに先ほどと同じようにログインして、データベースにtest_dbがあるかを確認し、存在すればボリュームの永続化ができています。
これで実践は終了なので、不要になったデータの後始末を行います。
docker rm -f db-server
docker rm -f db-client
docker volume rm my-db-data
docker network rm db-net
docker image rm postgres:18.3-alpine3.23 adminerまとめ
この記事では、以下の2つの概念を学びました。
ボリューム(名前付きボリューム):コンテナの寿命を超えてデータを守る仕組み。データベースなどの「状態(ステート)」を持つコンテンツで使います。
ネットワーク(ユーザー定義ネットワーク):コンテナ同士を名前(コンテナ名)でつなぐ仕組み。コンテナ名をホスト名としてDNSで自動的に名前解決するので、IPアドレスを気にせず、マイクロサービスを構築できます。
これらを組み合わせることで、ステートレスなコンテナを維持しながら永続的なデータの保存とネットワークによる通信を実施することができ、Dockerの強みを活かしたシステム構築が可能になります。