[Docker, Laravel9(Vite, Vue3, Inertia), Nginx, MySQL(非 Laradoc, 非 LarvelSail)] 将现有项目(单体架构)使用 Laravel9 进行 Docker 化的步骤总结如下

为什么写了呢?

最近我进行了一项工作,目的是将Laravel 9应用程序Docker化,而不使用Laradoc或LaravelSail,以便将来可以使用AWS的ECS。

在这个过程中,由于Laravel 9的单体架构的Docker化文章还很少,并且在构建部分适配新的前端构建工具Vite时遇到了一些困难,所以我写下了这篇文章,希望可以对其他人有所帮助,并且也作为备忘录的意义。

顺便说一下,关于使用Laravel9+Vue(使用Vite)构建微服务(将前端切换到不同的框架)的方法,我在下面的英文文章中找到了一些信息,所以我想与大家分享。

解决问题的源头文章是Stack Overflow的文章。

如果你对Laravel8感到困惑,因为有很多文章都能提供完整的步骤,所以我建议你参考其他人的文章。但是,我之前写过一些解决一些局部问题的文章,希望对你有所帮助(以下是相关文章)。

在构建docker环境中遇到的问题和解决办法进行了总结,使用的工具包括laravel8和nginx(非Laradoc)。

版本处理

Laravel9(使用vite、vue3、Inertia的单体架构)
PHP8.1
MySQL8.0
nginx1.23

完成的架构图

laravel9(モノリス)のアーキテクチャ図.drawio (5).png

操作步骤

注意:请注意,由于删除了不必要的内容,从现场运行的源代码中,请勿直接复制,可能无法正常工作。请将其视为参考资料使用。

1. 进入已经在运行的 Laravel9 项目的目录中。

根据架构图创建docker-compose.yml文件。


version: "3"

volumes:
  nginx-public: {} # fpmコンテナからnginxコンテナにpublicディレクトリのコピーする必要があるため、ローカルPCを経由してpublicディレクトリを保存させられるようにするためvolumesを用意します。

services:
  nginx:
    container_name: nginx
    build:
      context: .
      dockerfile: nginx.Dockerfile
      args:
        fpm_host: fpm
    depends_on:
      - fpm
    ports:
      - 3334:80
    volumes:
      - ./logs:/var/log/nginx
      - nginx-public:/var/www/public # publicディレクトリを一時保存場所のvolumesから呼び出しで/var/www/publicにコピー

  fpm:
    container_name: fpm
    build:
      context: .
      dockerfile: fpm.Dockerfile # コンテナの設定が入り組むので後ほど手作りする
    volumes:
      - .:/var/www # Laravelプロジェクトをfpmコンテナにコピー
      - nginx-public:/var/www/public # /var/www/publicディレクトリを一時保存場所のvolumesにコピー
    depends_on:
      - db
    networks:
      - default

  db:
    container_name: db
    image: mysql/mysql-server:8.0
    volumes:
      - ./logs/mysql:/var/log/mysql
      - ./local-docker/mysql/mysqld_charset.cnf:/etc/mysql/conf.d/mysqld_charset.cnf
      - ./local-docker/mysql/initdb.d:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_ROOT_HOST: '%'
      TZ: Asia/Tokyo
    ports:
      - 3306:3306
    command: mysqld --innodb_use_native_aio=0

networks:
  default:
    name: network-name

3. 在同一项目的顶级目录下创建各个Dockerfile。

# ===マルチステージビルドを2回行う(phpとnode)===
FROM node:16 as node-build # nginx:1.23に対応しているのがnode16系だったので16で設定
COPY . /application
WORKDIR /application
RUN apt-get update\
    && npm install \
    && npm run build --force

FROM php:8.1-alpine as builder
# dockerパッケージインストール
RUN set -eux && \
    apk update && \
    apk add --update --no-cache \
    autoconf gcc g++ make icu-dev libzip-dev libpng libpng-dev oniguruma-dev
RUN docker-php-ext-install pdo_mysql mbstring opcache zip fileinfo gd
RUN curl -SL http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz | tar -xz -C ~/ && \
    rm /usr/bin/iconv && \
    mv ~/libiconv-1.15 ~/libiconv && \
    ~/libiconv/configure --prefix=/usr/bin && \
    make && make install && \
    rm -rf ~/libiconv

# composerインストール
WORKDIR /app
COPY --from=composer:2.4 /usr/bin/composer /usr/bin/composer

RUN mkdir -p database/seeds && \
    mkdir -p database/factories  && \
    mkdir -p tests

# ===マルチステージビルドここまで===

# 実際に稼働するコンテナ
FROM php:8.1-fpm-alpine

COPY --from=builder /usr/bin/composer /usr/bin/composer
COPY --from=builder /usr/bin/lib /usr/bin/lib
COPY --from=builder /usr/lib /usr/lib
COPY --from=builder /usr/local/lib/php/extensions/no-debug-non-zts-20210902 /usr/local/lib/php/extensions/no-debug-non-zts-20210902
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=node-build /application/public /var/www/public

ENV IS_FPM=1

COPY ["docker/fpm/start.sh", "/"]
RUN chmod +x /start.sh

COPY docker/fpm/php.ini /usr/local/etc/php/php.ini

COPY . /var/www

ARG APP_ENV=local
ENV APP_ENV=$APP_ENV

WORKDIR /var/www

EXPOSE 80

CMD ["/start.sh"]

FROM nginx:1.23-alpine

ARG fpm_host=localhost # 本番環境立ち上げではfpm_hostを準備しないことで、この値がlocalhostになる
ENV FPM_HOST=$fpm_host # ローカルではfpm(コンテナ名)として使う

COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf.tmp
RUN envsubst '$FPM_HOST'< /etc/nginx/nginx.conf.tmp > /etc/nginx/nginx.conf

创建用于启动Dockerfile所需的文件。

error_reporting = E_ERROR | E_WARNING | E_PARSE | E_NOTICE
display_errors = stdout
display_startup_errors = on
log_errors = on
error_log = /var/log/php/php-error.log
upload_max_filesize = 100M
memory_limit = -1
post_max_size = 100M
max_execution_time = 900
max_input_vars = 100000
extension_dir = /usr/local/lib/php/extensions/no-debug-non-zts-20210902
# セキュリティを向上させるため、PHPのバージョンをレスポンスヘッダに含めないようにする
expose_php = Off

[Date]
date.timezone = "Asia/Tokyo"

[mbstring]
mbstring.language = "Japanese"

[opcache]
opcache.validate_timestamps = ${OPCACHE_VALIDATE_TIMESTAMPS}
opcache.revalidate_freq = 1

#!/bin/sh
set -e

# composerインストール
composer clear-cache
composer install
echo "Complete composer install !"

# Laravelのキャッシュをクリア
composer dump-autoload
php artisan clear-compiled
php artisan optimize
php artisan config:cache
php artisan cache:clear
php artisan route:clear
php artisan view:clear

# マイグレーション実行
php artisan migrate

# バージョン確認
php -v

echo "PHP Setup Complete."

# fpm起動
if [ $IS_FPM -eq 1 ] ; then
  php-fpm
else
  echo "Skip run fpm"
fi

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    server {
        listen 80;
        listen [::]:80;
        root /var/www/public;

        # Laravel公式推奨の(クリックジャッキングなどの)セキュリティ
        add_header X-Frame-Options "SAMEORIGIN";
        add_header X-Content-Type-Options "nosniff";

        index index.php;

        charset utf-8;

        location / {
            root /var/www/public;
            try_files $uri $uri/ /index.php$is_args$args;
        }

        location = /favicon.ico { access_log off; log_not_found off; }
        location = /robots.txt  { access_log offc; log_not_found off; }

        error_page 404 /index.php;

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

        location ~ /\.(?!well-known).* {
            deny all;
        }
    }

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;

    # アップロード上限36MB制限(35*1024^2 = 36.7MB)
    client_max_body_size 35m;

    # セキュリティを向上させるため、nginxのバージョンをレスポンスヘッダに含めないようにする
    server_tokens off;
}

-- DBとユーザーを作成
CREATE DATABASE IF NOT EXISTS `local_db`;
CREATE USER 'local_db'@'%' IDENTIFIED BY 'password';
GRANT ALL ON local_db.* TO 'local_db'@'%';
[mysqld]
character_set_server=utf8
character_set_filesystem=utf8
collation-server=utf8_general_ci
init-connect='SET NAMES utf8'
init_connect='SET collation_connection = utf8_general_ci'
skip-character-set-client-handshake

准备好env文件。

APP_NAME=laravel
APP_ENV=local
APP_KEY=base64:xxxxxxxxxxxxxxxxx
APP_URL=http://0.0.0.0:3334
APP_TIMEZONE=Asia/Tokyo

LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug # 本番でこのまま行くと危ないので注意

DB_HOST=db
DB_PORT=3306
DB_DATABASE=local_db
DB_USERNAME=local_db
DB_PASSWORD=xxxx

MIGRATION_ENV=local

VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

STORAGE_NAME=s3

执行docker-compose build

执行7.docker-compose up

希望您能作为参考的话,总体来说,步骤如上所述。

在Laravel9的Docker構築中,需要了解和Laravel8之間的區別。

在考虑Laravel9在网上的部署时(与nginx的连接),最需要注意的是说webpack.mix和Vite的编译方式的区别。

在Laravel8中,我们使用webpack.mix.json来管理assets的版本,并将webpack.mix.json独立于public目录之外进行操作。具体来说,public下的文件(比如css文件)与resources下的文件名相匹配。(在显示时,通过将id与文件名结合来进行操作。)
因此,我们可以在nginx容器中独立于fpm容器进行编译(npm run prod),并准备好public目录来运行。

然而,在Laravel9中,用于版本管理的manifest.json文件位于public文件夹下,public文件夹中的文件名会在编译(npm run build)时改变为包含id的格式。(这可能是为了提高处理速度而提前进行文件名转换处理。)
因此,如果尝试像Laravel8一样在”fpm容器和nginx容器都将编译到public中的源文件”上进行构建,就会失败。
具体来说,nginx读取了fpm容器中的manifest.json文件,但该文件与nginx容器内的public文件夹中的文件名不同。结果导致nginx在public文件夹中查找文件时找不到与最终读取的文件名相匹配的文件,从而显示一个空白页面。

如果要使用Vite来显示基于单体架构的应用,并使用nginx进行展示,请注意需要准备与fpm容器(包含laravel的容器)完全相同的public目录。

此外,对于Laravel9(Vite)而言,需要注意的是,使用npm run build命令无法清除缓存,必须使用npm run build –force命令。而对于Laravel8(webpack.mix),由于项目没有缓存的需求,仅需使用npm run prod命令即可。

2022年12月15日附加说明

在看 Qiita 的时候,我找到了下面这篇有参考价值的文章。
关于在 Docker 环境下使用 Laravel 9 + VITE 遇到的困难。

目前尚未在本地尝试过,但从这篇文章推测,如果在Vite中指定了server: {host: true},那么Vite会独立连接并监听5173端口,并在公共目录同步,前提是也指定了server: {host: true}。

所以,与其强行复制并同步本次的文章,不如将Vite相关的文件存储在nginx容器中的Vite文件夹中,并允许其使用5173端口,让fpm容器和nginx容器内的Vite彼此自动同步,这符合Vite开发者预期的行为。

如果只考虑实际环境,我认为使用自己的文章可能已经足够运行了。但是,我感到不便之处是每次部署都需要重新上载全部内容,而且还需要手动管理fpm容器和nginx容器的公共目录版本控制。但如果上述问题解决得当,我相信每次只需重新上载fpm容器就足够了。所以,我觉得你们最好是使用这种构建方式。

广告
将在 10 秒后关闭
bannerAds