89
76

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel 9 + VITEの開発環境をdockerで実現する方法

Last updated at Posted at 2022-10-09

Laravel 9でLaravel Mixの代わりに新しく導入されたViteを使えるLaravel開発環境を、docker上に実現する方法を紹介します。

ゴール

  • WindowsやMac上に、dockerを使ったLaravel 9開発環境を作る。
  • Laravel MixではなくViteを使う
  • サーバーはnginx, php, mysqlを別にする。
  • Viteの利点であるページリロード無しでのCSS/JS更新を実現する。

ところでViteの発音は?

Viteは「ヴィート」と発音します。フランス語だそうです。アメリカ人なら「ヴァイト」と読みそうですが、アメリカ人も「ヴィート」と読んでます。

ところでLaravel Mixではだめなのか?

Laravel 9の途中から、フロントエンド・ビルド・ツールとして長年標準だったLaravel Mixが非標準となりViteが標準となりました。
現時点ではLaravel 9公式でMixもサポートしています。したがって、現時点でMixを使うことに問題はない一方で、今後はViteに完全に置き換わる可能性も考えておいたほうが良いでしょう。

ところでなぜViteなのか?

Laravel Mixはwebpackを使っていますが、Viteは全く違う構造になっています。
詳しく見ていくと本記事の本題から外れるのでやめておきますが、webpackと比較したViteについてはざっと以下のようなイメージだけ持っていればよいかもしれません。

  • 開発中の再コンパイルが圧倒的に高速になる
  • ソースへの変更が瞬時にフロントページに反映される
  • フロントページでAjax通信したあとのページのままデザインや動作を調整できる
  • フロントページで動作しているReactのステートを変更せずソース変更が反映される
  • ViteはIE11をサポートしてるけど開発中はIE11で見られないなど、これまでになかった制限がある
  • でも本番リリース用のビルドはwebpackとあまり変わらない

前提

  • PHP 8の知識がある
  • Laravel 9の知識がある
  • Laravel 9に必要な環境構築ができる
  • Dockerで簡単な環境構築ができる

本投稿ではWindowsを使っているので、MacやLinuxで合わないところは読み替えてください。

Dockerに各サーバーを準備する

目指すフォルダ構成はこんな感じにします。(Docker関連のみ)

l9vitedev (プロジェクトフォルダ)
├ docker
│ ├ mysql
│ │ └ (中身たくさん)
│ ├ nginx
│ │ ├ logs
│ │ ├ default.conf
│ │ └ Dockerfile
│ └ php
│  ├ Dockerfile
│  └ php.ini
└ docker-compose.yml

Dockerをインストールする

ホストOSにDockerをインストールした状態から開始。

プロジェクトフォルダにDocker用ファイルを作る

今回はドキュメントフォルダの下にQiitaフォルダを作り、l9vitedevフォルダを作ります。
作り終わったら、そのフォルダの中にDocker用ファイルを作っていきましょう。

docker-compose.yml

まずはdocker-compose.ymlから。

docker-compose.yml
version: '3'
services:
  l9vitedev-nginx:
    container_name: "l9vitedev-nginx"
    build:
      context: ./docker/nginx
    depends_on:
      - l9vitedev-app
    ports:
      - 80:80
    volumes:
      - ./:/src

  l9vitedev-app:
    container_name: "l9vitedev-app"
    build:
      context: ./docker/php
    depends_on:
      - l9vitedev-mysql
    volumes:
      - ./:/src
      - /src/node_modules
      - /src/vendor
      - ./docker/php/php.ini:/usr/local/etc/php/php.ini

  l9vitedev-mysql:
    image: mysql:8.0.28
    command: --max_allowed_packet=32505856
    container_name: "l9vitedev-mysql"
    volumes:
      - ./docker/mysql:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=l9vitedev
    ports:
      - 3306:3306

  l9vitedev-redis:
    image: redis:alpine
    container_name: "l9vitedev-redis"
    ports:
      - 16379:6379

いくつかポイントを抑えます。

Webサーバーはnginxなので、appサーバー(PHPサーバー)を別に立ち上げてます。
→今回、後で出てくる説明で対象サーバーをわかりやすくするため意図的にWebサーバーとappサーバーを分けてます。Apache+PHPで同一サーバーにしても全く問題ありません。

Webサーバーはnginxで、ポート80をDocker外に見せて起動します。

AppサーバーはPHPが動作するようにし、ポートはDocker外に見せず、nginxからdocker内のネットワークで呼び出されて動作します。PHPの設定を指定しやすいように、php.iniだけdocker/phpフォルダで見えるようにしています。
また、/src/node_modulesフォルダと/src/vendorフォルダはボリュームマウントから外してます。これは依存ライブラリ群をマウント対象から外すことで動作の高速化を狙ってします。Viteを使った開発環境ではその特性上★超重要★で、これをしないとページのロードに平気で1分とかかかります。

DBサーバーはMySQLで、デフォルトポートの3306をDocker外に見せて起動します。
ポート3306をDocker外に見せなくても動作はしますが、開発中にHeidiSQLやSequel Aceを使ってDBの中身を見るためには必要です。MySQLユーザー情報は、ユーザーIDがroot、パスワードがenvironmentで指定したMYSQL_ROOT_PASSWORDになります。
MySQL(とredis)はここでイメージを指定し、個別のDockerfileを持たない設定とします。
max_allowed_packetオプションは指定しなくてもいいです。(が、指定したくなることが多いのでここに書いてます)

nginxのDockerfile等

まずは手動でプロジェクトフォルダ/docker/nginxフォルダを作ります。

nginxDockerfileを作ります。
同時に、最低限必要な設定であるdefault.confを記述します。

Dockerfile
FROM nginx:1.21
COPY ./default.conf /etc/nginx/conf.d/default.conf

さらに、nginxの動作を設定するdefault.confを作ります。

default.conf
server {
    
    listen 80;
    server_name _;

    client_max_body_size 1G;

    root /src/public;
    index index.php;

    access_log /src/docker/nginx/logs/access.log;
    error_log  /src/docker/nginx/logs/error.log;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;    
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass l9vitedev-app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
        
}

開発を進める上でnginxの設定を変更したい場合はここに記載することになります。

phpのDockerfile等

まずは手動でプロジェクトフォルダ/docker/phpフォルダを作ります。

phpDockerfileを作ります。
同時に、最低限必要な設定であるphp.iniを記述します。

Dockerfile
FROM php:8.1-fpm
RUN apt-get update \
    && apt-get install -y \
    git \
    zip \
    unzip \
    vim \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libmcrypt-dev \
    libpng-dev \
    libfontconfig1 \
    libxrender1

RUN docker-php-ext-configure gd --with-freetype --with-jpeg
RUN docker-php-ext-install gd
RUN docker-php-ext-install bcmath
RUN docker-php-ext-install pdo_mysql mysqli exif
RUN cd /usr/bin && curl -s http://getcomposer.org/installer | php && ln -s /usr/bin/composer.phar /usr/bin/composer

ENV NODE_VERSION=16.14.0
RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash
ENV NVM_DIR=/root/.nvm
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
RUN node --version
RUN npm --version

WORKDIR /src
ADD . /src/storage
RUN chown -R www-data:www-data /src/storage

大事なポイントは、ComposerNodeを入れている部分です。Laravel 9では、Composerは2以上、Nodeは16以上が必要です。

開発中にNodeのバージョンコントロールもしたい場合があるので、NVMも入れています。
gdなどいくつかライブラリを入れていますが、このあたりは趣味(私の場合、自分がよく使うレンタルサーバーに寄せてる)なので好きなライブラリを入れましょう。

php.ini
upload_max_filesize=256M
post_max_size=256M

上記では例としてupload_max_filesizepost_max_sizeを指定していますが、特に設定がなければ空のファイルでもOKです。

実行テストする

ターミナル(WindowsならPowerShell)を起動し、プロジェクトフォルダに移動します。

たとえばWindowsのPowerShellを起動して、ドキュメントフォルダのQiitaフォルダに作ったl9vitedevプロジェクトフォルダに移動した場合は:
image.png

ターミナルで、nginxとMySQLの起動に必要なフォルダを準備します。
※gitを使う場合、これらはignoreすべきで、gitからcloneした直後に実行すべきターミナルコマンドです。

# ログファイルを格納するディレクトリを作る(Gitを使う場合はこの``logs``フォルダをignoreすべき)
> mkdir ./docker/nginx/logs

# MySQLで使用するディレクトリを作る(Gitを使う場合はこの``mysql``フォルダをignoreすべき)
> mkdir ./docker/mysql

これでDocker起動に必要なすべてのファイルが揃いました。起動しましょう。

> docker compose up -d

うまく行けば以下のように4つのコンテナが正常起動します。

image.png

Webサーバーとして機能しているか確認するため、Chromeを開いてhttp://localhostにアクセスしてみましょう。
image.png

まだドキュメントルートに何もアップロードしてないので「File not found.」で正解です。サーバーは正しく起動できました。

Laravelをインストールする

Laravelのインストール先は、appサーバー=phpサーバーです。
appサーバーに入って、composerを使ってLaravelをインストールします。

appサーバーのコンソールに入る

> docker compose exec l9vitedev-app bash

以下のように入れたらOKです。

root@681925db0ad3:/src#

ここからは「>」がホストOS(ここではWindows)、「$」がDockerコンテナ内のOSとして表記します。
上記/src#の#部分が$だと思って読み替えてください。

サブフォルダにLaravelをインストールする

Laravelをプロジェクトフォルダ(l9vitedevフォルダ)にインストールします。
ただ、プロジェクトフォルダにはすでにdocker関連ファイルがあるので、以下のようにサブフォルダ「l9vitedev_tmp」に一旦インストールし、その後中身をプロジェクトフォルダに移動することにします。

まずサブフォルダにLaravelをインストール。ここではバージョン9を指定しています。

$ composer create-project "laravel/laravel=9.*" l9vitedev_tmp --prefer-dist

次に中身をプロジェクトフォルダに移動し、一時サブフォルダを削除。

$ mv l9vitedev_tmp/* ./
$ mv l9vitedev_tmp/.* ./
$ rm l9vitedev_tmp -rf

このとき以下のように、/.や/..が移動できないというメッセージ以外に問題がなければ成功です。

image.png

最終的に、フォルダ構成が以下のようになるはずです。

image.png

ここまでrootユーザーでLaravelをインストールしましたが、storageフォルダ以下のフォルダはWeb用のユーザーwww-dataが書き込みできなくてはいけないので、書き込み権限を付与しておきます。
これをやっておかないと、実行テストをした時点で「The stream or file "/src/storage/logs/laravel.log" could not be opened in append mode」というエラーになります。

$ chmod -R guo+w storage

Laravel公式のstorage利用手順にも指定のあるように、storageのシンボリックリンクを設置します。

$ php artisan storage:link

実行テストする

WebサーバーにアクセスするとLaravelが動作するはずなので、Chromeを開いてhttp://localhostにアクセスしてみましょう。

image.png

Viteを使う

この記事ではViteを使ってCSSを変更し、その変更がフロントに反映されるまでを実装してみます。

依存のインストール

$ npm install

ViewにViteを埋め込む

\resources\views\welcome.blade.phpをエディタで開き、以下のようにViteを埋め込みます。

welcome.blade.php
<!DOCTYPE html>
<html ...>
    <head>
        {{-- ... --}}
 
        @vite(['resources/css/app.css', 'resources/js/app.js'])
    </head>

埋め込もうとしているapp.cssapp.jsは、Laravelインストール時に自動生成されているファイルです。

まずはこの状態でhttp://localhostを見てみましょう。

image.png

Viteを起動していないので、当然エラーになるわけです。

Viteを起動する

次に、Viteを起動します。

$ npm run dev

image.png

起動成功したので、http://localhostを見てみましょう。

image.png

ブラウザのコンソールログにいくつかエラーが出ます。これは、Viteから提供されるはずのファイルが読めない状態です。
通信先は127.0.0.1:5173です。これが失敗しているのは、以下2つが原因です。

  • そもそもポート5173がDocker外に開かれていない
  • ViteはLocal:は見ているがNetwork:は見ていない

先程Viteを起動したときにLocal: http://localhost:5173/と表示されているのがローカル(appサーバー)に反応するという意味です。
Network: use --host to exposeとなっているのは、ネットワーク経由でもアクセスするなら--hostオプションをつけろという意味です。

appサーバーのポート5173を見せる

Docker外にいるブラウザから、appサーバー内にいるViteが見えるよう、ポート5173を見せるため、docker-compose.ymlにポートを追記します。

docker-compose.yml
...
  l9vitedev-app:
    container_name: "l9vitedev-app"
    build:
      context: ./docker/php
    depends_on:
      - l9vitedev-mysql
    ports:
      - 5173:5173
    volumes:
      - ./:/src
      - /src/node_modules
      - /src/vendor
      - ./docker/php/php.ini:/usr/local/etc/php/php.ini
...

ports:が追記されました。これでDocker外からlocalhost:5173にアクセスすると、appサーバーのポート5173につながります。

また、/docker/php/Dockerfileの先頭にもEXPOSEの設定が必要です。

Dockerfile
FROM php:8.1-fpm
EXPOSE 5173
RUN apt-get update \
...

Dockerfileへの変更を反映するには、Dockerコンテナを再起動する必要があります。
この時、composernpmのinstallコマンドを再実行することに注意してください。理由はこの記事の下部に書きます。

> docker compose down
> docker compose up -d --build
> docker compose exec l9vitedev-app bash
$ composer install
$ npm install

--host オプションを追加する

--host オプションは、Vite起動オプションなのでvite.config.jsまたはnpm run devの中身を変更することになります。どちらの方法でも大丈夫ですが、あとでvite.config.jsは追記するのでvite.config.js側に設定を集約するほうが後々楽でしょう。

vite.config.jsで設定する場合は、以下のようにserver hostを設定します。

vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        ...
    ],
    server: {
        host: true,
    },
});

package.jsonで設定する場合は、以下のように--hostオプションを追記します。

package.json
{
    "private": true,
    "scripts": {
        "dev": "vite --host",
        "build": "vite build"
    },
    ...
}

Viteを起動(すでに起動中の場合はCtrl+Cで停止してから再度起動)します。

$ npm run dev

今度はNetwork:も見ていることがわかります。

image.png

もう一度、http://localhostを見てみましょう。

image.png

またエラーが出ています。
今度はhttp://[::]:5173/以下のファイルが見えないというエラーです。
これはViteの真骨頂であるホットリロード機構HMRと通信できていないことを意味します。解決には、HMRの設定でホストをlocalhostにすればOKです。

vite.config.js
...
    server: {
        host: true,
        hmr: {
            host: 'localhost',
        },
    },
});

ファイルを保存したら、Viteを再起動して、もう一度、http://localhostを見てみましょう。

image.png

コンソールのエラーが消えました。

CSSを変更し、フロントに反映する

ではCSSを変更してみましょう。
/resources/css/app.cssをエディタで開くと空ですので、以下のように入力し保存します。

app.css
.underline{
  color: red;
}

ブラウザでhttp://localhostを見てみましょう。(Windowsでは)反映されていません。
※MacやLinuxの場合は問題が出ないと思われますので、読み飛ばしてください。

image.png

Viteを再起動して、もう一度、http://localhostを見てみましょう。

image.png

反映されました!
しかし、これではViteの最重要機能「CSS保存で瞬時に反映」が実現できていません。
問題は、appサーバー内でViteがファイル編集イベントに反応できていないことで起こっています。この問題はどうもWSL2に関連する問題のようで、Vite公式でも言及されています

こういった場合、Viteをポーリングモードで動作させることができます。Viteの設定ファイルにポーリングモードで動作するようusePollingを記述します。

vite.config.js
...
    server: {
        host: true,
        hmr: {
            host: 'localhost',
        },
        watch: {
          usePolling: true,
        },
    },
});

Viteを再起動し、ブラウザでhttp://localhostを開き、リンク文字が赤いことを確認したら、以下のようにリンク文字のCSS設定を黄色にして保存してみましょう。

app.css
.underline{
  color: yellow;
}

保存後すぐにViteが以下のように反応するはずです。

image.png

ブラウザ側でもすぐリンク文字が黄色くなります。

image.png

Dockerコンテナの操作におけるノウハウ

Dockerコンテナの起動と終了

起動

> docker compose up -d

リビルドと起動

> docker compose up -d --build

停止

> docker compose down

Dockerコンテナ起動時に毎回実行すべきコマンド

今回は、動作を高速にするために/src/node_modulesフォルダと/src/vendorフォルダはボリュームマウントから外してます。
これにより、composernpmでインストールされた依存ライブラリはDockerコンテナを終了するごとに消えてしまいます。したがって、docker compose upするときは毎回直後に依存ライブラリをインストールする必要があります。

> docker compose up -d
> docker compose exec l9vitedev-app bash
$ composer install
$ npm install

面倒だという方は起動時自動実行シェルスクリプトを組み込むことをおすすめします。それも面倒なら、appサーバーのDockerfileに以下の行を入れれば幸せになれるかも。

Dockerfile
ENTRYPOINT [ "bash", "-c", "composer install; npm install; exec php-fpm" ]

以上です!お疲れ様でした!

89
76
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
89
76

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?