[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
完成的架构图
操作步骤
注意:请注意,由于删除了不必要的内容,从现场运行的源代码中,请勿直接复制,可能无法正常工作。请将其视为参考资料使用。
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容器就足够了。所以,我觉得你们最好是使用这种构建方式。