技術資料

Windows 11でDocker Composeを使ってLaravel環境を作る:Container・Volume・Networkの基本

作成日:2026.10.01

Windows 11のDocker Desktop上に、DockerfileとDocker Composeを使ってLaravel・PHP・MySQL・Mailpitの開発環境を構築します。ImageとContainerの関係、Bind MountとNamed Volumeによるデータの保存、ポート公開とContainer間通信の違いを実例で確認。LaravelからMySQLへ接続するときに、localhostではなくService名を使う理由も説明します。

Dockerを使い始めると、Image、Container、Volume、Networkなどの用語が出てきます。今回はWindows 11のDocker Desktop上にLaravel、PHP、MySQL、Mailpitの開発環境を作りながら、それぞれの役割を確認します。

Laravel Sailは使わず、Dockerfileとcompose.yamlを用意します。特に、Containerを削除したときにデータがどうなるか、LaravelからMySQLへ接続するときにlocalhostではなくService名を使う理由を見ていきます。

今回の環境

対象はWindows 11、Docker DesktopのWSL2バックエンドです。手元の環境では、次のバージョンを使いました。

Docker version 29.8.1
Docker Compose version v5.5.1
PHP 8.3.35
Composer 2.10.3
MySQL 8.0

Docker Desktopをインストールした後、PowerShellでDocker CLIとComposeを確認します。

docker --version
docker compose version
docker run hello-world

hello-worldの実行まで完了すれば、Docker Engineが起動し、Imageの取得とContainerの実行ができています。Docker Desktopのインストール条件やWSL2の設定は、Docker DesktopのWSL2バックエンドに関する公式資料を確認してください。

ImageとContainerの関係

Imageは、Containerを作るときに使うファイルや設定をまとめたものです。ContainerはImageをもとに起動する実行単位です。

Dockerfile
    ↓ build
Image
    ↓ run
Container

たとえば、PHPの公式Imageを土台にPHP拡張やComposerを追加したものをImageとして作り、そのImageからLaravelを実行するContainerを起動できます。ImageとContainerの詳しい説明は、Docker公式ドキュメントのImageの説明とContainerの説明も参照してください。

PHP用ImageとComposeの設定を作る

作業用ディレクトリを用意し、Docker関連のファイルとLaravel本体を分けます。

New-Item -ItemType Directory -Path D:\dev\docker-laravel-study\src -Force
Set-Location D:\dev\docker-laravel-study

まず、PHP用Imageを作るDockerfileを作成します。

FROM php:8.3-cli

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        git \
        unzip \
        libzip-dev \
        libonig-dev \
    && docker-php-ext-install \
        pdo_mysql \
        mbstring \
        zip \
    && rm -rf /var/lib/apt/lists/*

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

php:8.3-cliはPHP公式ImageのCLI版です。MySQL接続用のpdo_mysql、Laravelで使うmbstring、Composer依存関係で使うzip拡張を追加しています。公式PHP Imageには拡張を有効化するためのdocker-php-ext-installなどの補助スクリプトがあります。詳細はPHP公式Docker Imageの説明を参照してください。

次に、Laravel、MySQL、Mailpitをまとめて起動するcompose.yamlを作ります。

services:
  app:
    build:
      context: .
    working_dir: /var/www/html
    volumes:
      - ./src:/var/www/html
    ports:
      - "127.0.0.1:8000:8000"
    command: php artisan serve --host=0.0.0.0 --port=8000
    depends_on:
      db:
        condition: service_healthy
      mailpit:
        condition: service_started

  db:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_USER: laravel
      MYSQL_PASSWORD: local-password
      MYSQL_ROOT_PASSWORD: local-root-password
    ports:
      - "127.0.0.1:33060:3306"
    volumes:
      - mysql-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-plocal-root-password"]
      interval: 5s
      timeout: 5s
      retries: 10

  mailpit:
    image: axllent/mailpit:latest
    ports:
      - "127.0.0.1:8025:8025"

volumes:
  mysql-data:

Composeファイルでは、関連するContainerをServiceとして定義します。今回はapp、db、mailpitの3つです。Composeは同じプロジェクトのServiceを共通のNetworkへ接続するため、Service名を使って相互に通信できます。

depends_onのservice_healthyは、MySQLのhealthcheckが成功してからappを起動する指定です。service_startedはMailpitのContainerが起動したことを条件にします。起動したContainerの中のアプリケーションが、すべての処理を終えて利用可能になったことまで保証するものではありません。

MySQLのユーザー名とパスワードは学習用の値です。本番環境では使わず、実際の認証情報をGit管理するComposeファイルへ直接書かないでください。axllent/mailpit:latestは更新されるタグなので、同じImageで再現したい場合は利用時点のバージョンを確認して固定します。

ポートは127.0.0.1にバインドしています。この指定により、ホストPCから確認できる一方、ホストの他のネットワークインターフェースへ公開しない設定になります。Dockerではポートを公開するとホスト外からも到達できる場合があるため、開発用サービスの公開範囲を確認してください。詳しくはDockerのPort publishingに関する説明を参照してください。

Laravelを作成して接続先を設定する

Composeの設定を確認してから、Docker ImageをBuildします。

docker compose config
docker compose build app

Laravel本体は、作成したPHP用ImageのComposerで生成します。

docker compose run --rm --no-deps app composer create-project laravel/laravel .

./src:/var/www/htmlは、Windows側のsrcディレクトリをContainer内の/var/www/htmlへBind Mountする設定です。Container内で生成したLaravelのファイルは、Windows側からも確認できます。

Laravelのsrc/.envで、MySQLの接続先を次のように設定します。

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=local-password

Mailpitへ接続するためのメール設定も行います。

MAIL_MAILER=smtp
MAIL_SCHEME=null
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.test"
MAIL_FROM_NAME="${APP_NAME}"

Containerから別のServiceへ接続するときは、DB_HOST=dbやMAIL_HOST=mailpitのようにComposeのService名を指定します。接続先のIPアドレスはContainerの再作成で変わる可能性がありますが、Service名は同じNetwork内で名前解決できます。

Laravel・MySQL・Mailpitを起動する

まず、MySQLとMailpitを起動します。

docker compose up -d db mailpit
docker compose ps

MySQLの状態がhealthyになったら、LaravelからMigrationを実行します。

docker compose run --rm app php artisan migrate

続けてLaravelを起動します。

docker compose up -d app

ブラウザーで次のURLを開き、Laravelのスタートページを確認します。

http://127.0.0.1:8000

Mailpitの画面はhttp://127.0.0.1:8025で開けます。Laravelからメールを送ってみる場合は、LaravelのTinkerを起動します。

docker compose exec app php artisan tinker

Tinker上でテストメールを送ります。

Illuminate\Support\Facades\Mail::raw(
    'Docker + Laravel + Mailpit のテストメールです。',
    function ($message) {
        $message->to('dev@example.test')
            ->subject('Mailpit test');
    }
);

Mailpitの画面にメールが表示されれば、LaravelからSMTPへの接続を確認できます。Mailpitは開発・テスト用であり、実際の宛先へメールを配信するサービスではありません。既定のSMTPはポート1025で、暗号化や認証を行わないため、本番用メールサーバーとして使わないでください。詳しくはMailpitの設定資料を参照してください。

ホストPCとContainerのポートを区別する

Composeのportsは、ホストPCのポートをContainer内のポートへ割り当てる設定です。たとえば、127.0.0.1:33060:3306はホスト側の33060番ポートをMySQL Containerの3306番ポートへつなぎます。

接続元 接続先 用途
Windowsのブラウザー 127.0.0.1:8000 Laravelの画面
WindowsのMySQLクライアント 127.0.0.1:33060 必要に応じてDBを直接確認
Windowsのブラウザー 127.0.0.1:8025 MailpitのWeb UI
LaravelのContainer db:3306 MySQLへの接続
LaravelのContainer mailpit:1025 MailpitのSMTPへの接続

LaravelとMySQLは同じCompose Networkにいるため、LaravelからMySQLへ接続するだけならMySQLのports設定は必要ありません。ここではWindows上のDBクライアントからも接続できるよう、ホスト側ポートを割り当てています。MailpitのSMTPポート1025も、Container間だけで使うためホストへ公開していません。

Bind MountとNamed Volumeでデータの残り方を比べる

Dockerでは、Container内のファイルをどこに保存するかによって、Containerを削除したときの挙動が変わります。今回の構成には3種類あります。

データ 保存方法 Container削除後
Laravelのソース Bind Mount(./src:/var/www/html) Windows側に残る
MySQLのデータ Named Volume(mysql-data) Volumeに残る
Mailpitの受信メール Container内のみ Containerと一緒に消える

Laravelソースはホスト側のディレクトリをContainerへ見せています。MySQLのデータは、Dockerが管理するNamed Volumeへ保存しています。MailpitにはVolumeを設定していないため、Containerを削除すると受信メールもなくなります。

Containerの削除と再作成を確認するため、次を実行します。

docker compose down
docker compose up -d

docker compose downは、このプロジェクトのContainerとNetworkを削除しますが、Composeファイルに定義したNamed Volumeは通常そのまま残します。再起動後、Migrationの状態を確認します。

docker compose exec app php artisan migrate:status

今回の確認では、Containerを削除して作り直した後も、MySQLのNamed Volumeに保存したMigrationの状態が残っていました。一方、Volumeを設定していないMailpitの受信メールは残りませんでした。Named Volumeも削除するdocker compose down --volumesは、保存データを消す操作なので注意してください。詳細はdocker compose downの公式資料を参照してください。

Container内のlocalhostとService名の違い

LaravelのContainerからMySQLへ接続するときは、DB_HOST=dbを使います。localhostや127.0.0.1は、接続元であるLaravelのContainer自身を指すためです。

今回の環境では、PDOからそれぞれの接続先を指定して挙動を確認しました。まず、Service名dbでは接続できました。

docker compose exec app php -r 'try { new PDO("mysql:host=db;port=3306;dbname=laravel", "laravel", "local-password"); echo "connected", PHP_EOL; } catch (Throwable $e) { echo $e->getMessage(), PHP_EOL; }'

localhostを指定した場合は、次のエラーになりました。

SQLSTATE[HY000] [2002] No such file or directory

この環境のPDO MySQLでは、ホストにlocalhostを指定するとUnix socket経由で接続しようとし、そのsocketがLaravelのContainer内にないため失敗しました。さらに、127.0.0.1を指定した場合はLaravelのContainer自身へTCP接続しようとし、MySQLが動いていないため次のエラーになりました。

SQLSTATE[HY000] [2002] Connection refused

接続先を整理すると、次のようになります。

DB_HOST=db
→ Compose Networkでdb Serviceへ接続

DB_HOST=localhost
→ この環境ではContainer内のUnix socketへ接続しようとして失敗

DB_HOST=127.0.0.1
→ Laravel自身のContainerへTCP接続しようとして失敗

localhostのエラー内容は、クライアントライブラリや接続方法によって異なる場合があります。大事なのは、Container内のlocalhostがWindowsやMySQLのContainerを指すわけではないことです。同じCompose Network上のServiceへ接続するときは、Service名とContainer側のポートを使います。

今回確認できたこと

Laravelの開発環境をDocker Composeで作ると、複数のContainerをひとまとまりの構成として扱えます。それぞれの役割を今回の環境に当てはめると、次のようになります。

  • DockerfileからPHP用のImageを作り、そのImageからapp Containerを起動する。
  • ComposeのService名を使い、LaravelからMySQLやMailpitへ接続する。
  • LaravelのソースはBind Mount、MySQLのデータはNamed Volumeへ保存する。
  • ホストPCから画面へアクセスするためのポート公開と、Container同士の通信を分けて考える。
  • Containerを削除しても、ホスト側やVolumeへ保存したデータは残る。Container内だけのデータは削除される。

最初から多くの機能を覚えるより、短いDockerfileとcompose.yamlで起動し、データや接続先がどう変わるかを確かめていくと理解しやすいと思います。Containerは作り直せる実行環境として扱い、残したいデータはBind MountやNamed Volumeなど保存先を明示しておきましょう。

参考資料

この記事を書いた人

※上が私です。

奈良市を拠点に、27年以上の経験を持つフリーランスWebエンジニア、阿部辰也です。

これまで、ECサイトのバックエンド開発や業務効率化システム、公共施設の予約システムなど、多彩なプロジェクトを手がけ、企業様や制作会社様のパートナーとして信頼を築いてまいりました。

【制作会社・企業様向けサポート】
  • 専任エンジニアのいない企業様に対するシステム面の不安を解消
  • 柔軟な契約形態や短納期での対応により、急なニーズにも迅速にサポート
  • システムの企画段階から運用まで、ワンストップでのサービスを提供

Webシステムの開発やサイト改善でお困りの際は、どうぞお気軽にご相談ください。小さな疑問から大規模プロジェクトまで、最適なご提案を心を込めてさせていただきます。

ぜひ、プロフィールやWeb制作会社様向け業務案内、一般企業様向け業務案内もご覧くださいね。

Laravelのコードレビューで遅いSQLの候補を洗い出す

2026.10.02

Laravelのコードレビューで、ループ内のDBアクセスやEloquentのN+1、大量取得、検索条件とインデックスなど、性能問題につながりやすい箇所を候補として洗い出します。Laravelで発行SQLと実行時間を確認し、MySQLのEXPLAINや実データに近い環境での計測へつなげる方法を紹介します。コードレビューだけではSQLの実行時間を断定できない点も説明します。

Laravel MySQL PHP

LaravelのArtisanコマンドを本番運用する:冪等性・二重実行防止・再実行の設計

2026.09.22

LaravelのArtisanコマンドでデータベースを更新する処理を、本番環境で安全に運用するための考え方を整理します。商品在庫の一括更新を例に、冪等性、dry-run、チャンク処理、トランザクション、二重実行防止、失敗後の再実行、ログや終了コード、実行前後の確認手順を紹介します。

Laravel PHP

Laravel 13のNamed Rate Limiterでレート制限を実装する:IP・ユーザー・入力値ごとの設定

2026.09.19

LaravelのNamed Rate Limiterを使い、無制限に繰り返されたくない処理へレート制限を追加する方法を解説します。RateLimiter::for()とthrottleミドルウェアの基本から、IPアドレス・ユーザーID・入力値による制限、複数の制限、429レスポンス、ログイン処理への応用、Redis利用時の注意点まで整理します。

Laravel PHP

Laravel SocialiteでGoogleログインを実装する|OAuth設定から初回登録まで

2026.09.18

Laravel Socialiteを使ったGoogleログインを、OAuthクライアントの設定からコールバック、Laravelユーザーの登録・ログインまで解説します。Googleアカウントは変更されるメールアドレスではなくsubで識別し、初回登録時は検証済みメールアドレスを確認します。既存アカウントへ自動連携しない設計や、セッション管理、Socialite Fakeで確認するテスト項目も紹介します。

Laravel OAuth PHP

阿部辰也へのお仕事の依頼・お問い合わせ

軽いご相談もお気軽にどうぞ!

個人情報の取り扱いについて *必須 プライバシーポリシーをご確認いただき、同意いただける場合は「同意する」にチェックをしてください。

keyboard_double_arrow_up
TOP