👌

【ソース有】【チュートリアル】ECSでlaravel(11)をSQLiteでとり急ぎ動作させる(改訂版) 〜 イメージの作成まで

2025/02/06に公開

https://zenn.dev/catatsumuri/articles/258db6b60594f1

基本的には前回の続き。ただし今回一応サラの環境で出来るようにするから、前のものを読まなくても大丈夫なようにしたい。

必要なこと

  1. laravelの動作を単体で確認できる環境を作る
  2. dockerイメージを作る
    • dockerイメージの出来をローカルで確認する
  3. dockerイメージをECRに転送する
  4. タスク定義作る
  5. ECS Farget (サービス)でよきにはからう

今回は 2. まで行う

作成環境

EC2のt4g.small。まあまあ容量食うのでボリュームは20Gほど用意した。

ワーキングディレクトリ

mkdir ecs-laravel
cd ecs-laravel

をワーキングディレクトリとする

1. laravelの動作を単体で確認できる環境を作る

ワーキングディレクトリの下にlaravelアプリケーションを配置する。

laravel.buildからコードを作成する

ここではある程度作られたソースコードをcloneしてもいいのだけど、1から出来るイメージを作るために、敢えてlaravel.buildを用いてお手軽に完成品となるものを作成してみる。

curl -s "https://laravel.build/laravel-app?with=mysql" | bash


実行イメージ

するとworkingディレクトリの下にlaravel-appという名前でアプリケーションの雛形がセットされる

laravelアプリケーションを起動する

ここでは完成品の動作確認をするフェーズなのだが、そもそも完成していないのでこれを完成させていくという事になる。

docker-compose.yamlで不要なmysqlを削除し、.envを変更して起動

cd laravel-app

docker-compose.ymlというのがあるので、以下のようにmysqlの部分をカットする

docker-compose.yml
# 略
            IGNITION_LOCAL_SITES_PATH: '${PWD}'
        volumes:
            - '.:/var/www/html'
        networks:
            - sail
#        depends_on:
#            - mysql
        depends_on:
            - mysql
#    mysql:
#        image: 'mysql/mysql-server:8.0'
#        ports:
#            - '${FORWARD_DB_PORT:-3306}:3306'
#        environment:
#            MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
#            MYSQL_ROOT_HOST: '%'
#            MYSQL_DATABASE: '${DB_DATABASE}'
#            MYSQL_USER: '${DB_USERNAME}'
#            MYSQL_PASSWORD: '${DB_PASSWORD}'
#            MYSQL_ALLOW_EMPTY_PASSWORD: 1
#        volumes:
#            - 'sail-mysql:/var/lib/mysql'
#            - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
#        networks:
#            - sail
#        healthcheck:
#            test:
#                - CMD
#                - mysqladmin
#                - ping
#                - '-p${DB_PASSWORD}'
#            retries: 3
#            timeout: 5s
networks:
    sail:
        driver: bridge
#volumes:
#    sail-mysql:
#        driver: local

さらに .envmysqlからsqliteに変更する

DB_CONNECTION=sqlite
#DB_CONNECTION=mysql
#DB_HOST=mysql
#DB_PORT=3306
#DB_DATABASE=laravel
#DB_USERNAME=sail
#DB_PASSWORD=password

このように変更する。

なお、こちらの完成品はport8000で動作させるようにするため

APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:sBAiRUN+hy/Yp4FglKlR1Saloc8YFRgRkYbgZ3y6sf8=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=http://localhost
APP_PORT=8000 #<---------------------------------------

APP_PORT=8000を与えている。

この段階で

./vendor/bin/sail up

し、ホスト:8000にアクセスすると


laravelが起動している

となる。Ctrl+C で停止し、よさそうであれば

./vendor/bin/sail up -d

として裏に回すか、あるいは別端末で操作する。

その後

./vendor/bin/sail artisan --version

などして、sail環境からartisanが起動できる事を確認する

DBのmigration

./vendor/bin/sail artisan migrate:fresh --seed

と入力してみると

このようになる。

これはdatabase/database.sqlite にデーターが格納されるのだが、laravel.buildから展開した場合はこの辺が割と整っているのでこの辺りを全て使っていく。

laravel breezeの展開

さらに、laravel breezeをインストール、展開する。何故これが必要かというと、laravelのwelcomeページは全てがオールインワンになっており確認としては全く訳に立たないからである。

というわけでサクっとscaffoldする場合breezeが良い。とり急ぎbladeでいいと思う

./vendor/bin/sail composer require laravel/breeze --dev
./vendor/bin/sail artisan breeze:install blade
./vendor/bin/sail artisan migrate:fresh --seed

これで

ログインのフォームが用意される。今回はこれをECS上で確認する事が目標となる。

sail(開発環境)の終了

sailは「開発用のdocker環境」なので、開発が終わったら終了する。ここではbreezeを展開するという開発っぽいのに使っただけだが、必要に応じて起動したり終了してもらえたらよいと思う。

./vendor/bin/sail down

で終了できる

要するに、ここで完成したlaravelアプリケーションをデプロイするという作業を行うわけだ。

2. dockerイメージを作る

ここからいよいよ本題。ここまでは単純にlaravelアプリを作ってきただけ。

なお、ここではnginxphp-fpmを分離して動作させる環境を作成していく。

docker-compose.ymlを作る

ワーキングディレクトリにこのようなdocker-compose.yamlを作る

docker-compose.yml
services:
  php-fpm:
    image: php:8.4-fpm
    networks:
      - app-network

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    depends_on:
      - php-fpm
    networks:
      - app-network

networks:
  app-network:

こうした時に

ecs-laravel
├── docker-compose.yml # AWSに送信するイメージ生成確認用のdocker-compose 
└── laravel-app
    └── docker-compose.yml # 開発用のdocker-compose(sailが使う)

このような構成になっているはずである。この状態で

docker-compose up

して一応起動する事を確認する

当該のIP:80にアクセスすると

nginxのトップ画面が表示される

ソースコード入りカスタムイメージを作っていく

現在は

    image: php:8.4-fpm
# ...
    image: nginx:alpine

などの指定によりオフィシャルイメージ(これはdocker hubから取得される)を「そのまま」 使って起動しているだけに過ぎず、何のソースコードも挿入されていない。これを「カスタム」して自分のイメージを作る作業が本稿の要旨である。

ここでカスタムイメージ用のディレクトリを作成する。基本的にはphp-fpmnginxの2つのイメージを分割して作成した方がわかりやすいので、とりあえず2つディレクトリを切っていく

mkdir php-fpm nginx

1つずつ見ていくことにしよう

nginxを元にしたカスタムイメージの作成

nginx/Dockerfile
FROM nginx:alpine

WORKDIR /usr/share/nginx/html

COPY ./laravel-app/public /usr/share/nginx/html # laravelアプリケーションのpublicのみ転送している

EXPOSE 80

nginxにはlaravelのpublicファイルのみ基本的には必要であるので、上記の例でわかるようにlaravel_appの中のpublicの中身のみ転送している。というのもnginxではphpは実行できないため、各種phpファイルは必要がないからだ。

このDockerfileを作ったらビルドする

docker build -t my-ecs-nginx -f ./nginx/Dockerfile . --no-cache

このようにビルドされてくる。このイメージをdocker-composeで使うように指示する

services:
  php-fpm:
    image: php:8.4-fpm
    networks:
      - app-network

  nginx:
    #image: nginx:alpine
    image: my-ecs-nginx
    ports:
      - "80:80"
    depends_on:
      - php-fpm
    networks:
      - app-network

networks:
  app-network:

docker-composeを再起動する

docker-compose down
docker-compose up -d

確認

http://server/robots.txt とかにアクセスして内容が取れていればok


robots.txtの内容が表示されている

トップページはまだnginxのdefaultのままである。これは設定を何も変更していないためだ。

これを変更するために、nginxの設定ファイルを取得した後、変更しimageに詰め直していく。

nginxのイメージをシェルで入って確認する

docker-composeが起動している状態にて

docker-compose exec nginx sh

とする(bashが入ってないため)なお、

cat /etc/nginx/conf.d/default.conf

などしてnginxの設定ファイルの場所とか存在とかを確認しておく。


nginxのデフォルトconfが出力されている

さらに

find /usr/share/nginx/html/

などして、転送されたファイルの一覧を一応確認してみよう

これらが転送されたファイルであり、また現在のドキュメントルートである。現在 / でアクセスした場合 index.html が表示されている


index.htmlの中身

http://server/robots.txt でアクセスした場合は /usr/share/nginx/html/robots.txt が取れるというわけだ。なお、http://server/index.php でアクセスした場合はソースコードがそのまま落ちてくる。

あらかた確認が終わったらexitで出る

dockerコンテナーから /etc/nginx/conf.d/default.conf をホストに持ってくる

ここからホストにdockerイメージのファイルを転送する。これはまずdocker psで確認して

docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED       STATUS       PORTS                               NAMES
aacd50756247   my-ecs-nginx   "/docker-entrypoint.…"   2 hours ago   Up 2 hours   0.0.0.0:80->80/tcp, :::80->80/tcp   ecs-laravel_nginx_1
a5a73c914865   php:8.4-fpm    "docker-php-entrypoi…"   2 hours ago   Up 2 hours   9000/tcp                            ecs-laravel_php-fpm_1

ここでNAMESを見るとecs-laravel_nginx_1なので

docker cp ecs-laravel_nginx_1:/etc/nginx/conf.d/default.conf ./nginx/default.conf

のようにすると転送できる。

このconfigをカスタムする。ここでは全文を貼り付ける

nginx/default.conf
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;
    index  index.php;

    location / {
        root   /usr/share/nginx/html;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        fastcgi_pass   php-fpm:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /srv/public$fastcgi_script_name;
        include        fastcgi_params;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

このように、index index.phpにするのと、phpの設定を生やす。

まず

fastcgi_pass   php-fpm:9000;

ここで外部ホストと通信している事を確認する。つまり php-fpm:9000 というホストと通信している設定である。

さらに

fastcgi_param  SCRIPT_FILENAME  /srv/public$fastcgi_script_name;

これはphp-fpm側のパスが /srv/public に配置されているという想定になる。すなわち、php-fpm側のソースは全て /srv に転送される事を想定している。

nginxの設定ファイルを詰めなおす

nginx/Dockerfile
FROM nginx:alpine

WORKDIR /usr/share/nginx/html

COPY ./laravel-app/public /usr/share/nginx/html

COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

このように、nginx/default.conf/etc/nginx/conf.d/default.conf にコピーしているだけ

php-fpmのカスタムイメージを作成し、全て転送する

上記の例でみたように /srv にファイルを送信する

php-fpm/Dockerfile
FROM php:8.4-fpm

WORKDIR /srv

COPY ./laravel-app /srv

EXPOSE 9000

こんな感じになる

laravelを起動する

まずdocker-compose.ymlを修正

docker-compose.yml

services:
  php-fpm:
    #image: php:8.4-fpm
    image: my-ecs-php-fpm # カスタムイメージを使う
    networks:
      - app-network

  nginx:
    #image: nginx:alpine
    image: my-ecs-nginx
    ports:
      - "80:80"
    depends_on:
      - php-fpm
    networks:
      - app-network

networks:
  app-network:

それぞれビルドして

docker build -t my-ecs-nginx -f ./nginx/Dockerfile . --no-cache
docker build -t my-ecs-php-fpm -f ./php-fpm/Dockerfile . --no-cache

完了後

docker-compose down; docker-compose up -d

などする。この段階で当該IPへアクセスすると

パーミッションエラーになるが、この段階でlaravelと連携できている事が確認できる

作成されたイメージの修正

php-fpm側の権限修正

FROM php:8.4-fpm

WORKDIR /srv

COPY ./laravel-app /srv

RUN chown www-data storage database -R    # storageとdatabaseの権限を変更

EXPOSE 9000

というような事して再度ビルド

docker build -t my-ecs-php-fpm -f ./php-fpm/Dockerfile .

ダウン→アップ

docker-compose down; docker-compose up -d

すると

このように一応のトップページが現われる

実際に動作してはいない

Loginなどを押すと404になるはずだ。

これを修正する。

https://laravel.com/docs/11.x/deployment#nginx

を参考にすると

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

が必要なのがわかるので、これを付け足す

nginx/default.conf
    location / {
        root   /usr/share/nginx/html;
        try_files $uri $uri/ /index.php?$query_string; # 追加
    }

nginxをビルドして

docker build -t my-ecs-nginx -f ./nginx/Dockerfile . --no-cache

起動しなおす

docker-compose down; docker-compose up -d

これでアクセスするとログインフォームは出てくる


とりあえずログインフォームは出た

ここまでのソースコード

https://github.com/catatsumuri/ecr-ecs-fargate-setup/tree/main/001-laravel-setup

現在の構成の問題点を1つずつ潰す

sqliteの問題

まず、sqliteのデーターベースファイルであるdatabase/database.sqliteをそのまま転送してしまっているが、通常この手のシステム構成は外部DBシステム(RDSなど)を見にいくはずなので、イメージを作成する時に初期化などは必要がないはずである。

ただ、ここではデーターを転送するにあたって、laravelのツリーの中で一度migrationされた物理ファイルを作成したものを転送しているという割と見かけない構成になっている事を意識しておく必要があるだろう。

転送しないファイル

通常、 .envvendor/ というファイルはオリジナルソースに含まれていないか、あるいは含まれていたとしても転送しない方がデプロイの常道であるため、これを外していく。ただ、いきなり全て外すと解説が面倒なのでちょっとずつやる

.envの問題 (applicationキー)

では.envを転送しないようにしてみよう。これはプロジェクトのrootに .dockerignoreというファイルを作成する

.dockerignore
laravel-app/.env

これでphp-fpmをビルドして再起動する

docker build -t my-ecs-php-fpm -f ./php-fpm/Dockerfile . --no-cache
docker-compose down; docker-compose up -d

すると.envが転送されていないため、以下のようなエラーとなるはずだ

これに対応する

php-fpm/Dockerfile
FROM php:8.4-fpm

WORKDIR /srv

COPY ./laravel-app /srv

RUN chown www-data storage database -R
RUN cp .env.example .env

EXPOSE 9000

そうすると予想通り?

APP_KEYがセットされない問題が生じてくる。これはもちろん、Dockerfileの中でartisan key:generateしても動作するのではあるのだが、通常これは外部環境変数の注入で対応するのがこの手のデプロイの常道であるはずだ。

docker-compose exec php-fpm php artisan key:generate --show

などしてキーを書き込まず

services:
  php-fpm:
    #image: php:8.4-fpm
    image: my-ecs-php-fpm
    networks:
      - app-network
    environment:
      APP_KEY: base64:slBwwE6jbHjJSLHAlWbVvm9WUeqJrAQO/QaMrbdfcuA=

このような形でdocker-composeの環境変数で定義した方がより実践的だ。docker-composeを再度起動して正常に戻る事を確認しておく

vendorが転送されている問題

vendorに関しては元のlaravelツリーで構成してコピーしても動くといえば動くのだが、これに関してはやはりイメージ作成時に中でcomposer installするのが常道って感じがするので、そのようにする。

.dockerignore
laravel-app/.env
laravel-app/vendor/

ここでvendorを転送しないようにし

docker build -t my-ecs-php-fpm -f ./php-fpm/Dockerfile . --no-cache
docker-compose down; docker-compose up -d

するとvendorを転送していないので

このようになる

composerの対応

これはphp-fpmのDockerfile中でcomposer installする。なお、composerを動作させるために必要なパッケージも導入している

php-fpm/Dockerfile
FROM php:8.4-fpm

WORKDIR /srv

COPY ./laravel-app /srv

RUN chown www-data storage database -R
RUN cp .env.example .env

# 必要package
RUN apt-get update && apt-get install -y \
    git \
    unzip \
    zip

COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN composer install

EXPOSE 9000

再度ビルド

docker build -t my-ecs-php-fpm -f ./php-fpm/Dockerfile . --no-cache
docker-compose down; docker-compose up -d

これで再度アクセスするとビルドされているはずだ。

フロントエンドの対応

ここまではバックエンド側を潰してきたが、npm installなどはフロントエンドでやるのが普通である。つまりpublic/buildなどの生成である。

.dockerignoreのアップデート

.dockerignore
laravel-app/.env
laravel-app/vendor/
laravel-app/node_modules/
laravel-app/public/build/

としてそれぞれビルドしてみよう

docker build -t my-ecs-php-fpm -f ./php-fpm/Dockerfile . --no-cache
docker build -t my-ecs-nginx -f ./nginx/Dockerfile . --no-cache
docker-compose down; docker-compose up -d

すると

このようになる。

Vite manifest の問題

これは、たとえばフロントエンドでnpm installしnpm run buildしてpublic/buildを作成したとしても

public/build/manifest.json
  "resources/css/app.css": {
    "file": "assets/app-BwHaPEbd.css",
    "src": "resources/css/app.css",
    "isEntry": true
  },
  "resources/js/app.js": {
    "file": "assets/app-CAkSn3BF.js",
    "name": "app",
    "src": "resources/js/app.js",
    "isEntry": true
  }
}

のようなmanifest.jsonをバックエンドに送らないといけない。これは別々にDockerfileを作っているとうまいことやり辛いので、ここでnginxとphp-fpmのDockerfileを統合したsrvというディレクトリを作る事にする。

mkdir srv
cp nginx/default.conf srv

srvの中にconfigをコピーしている

srv/Dockerfileに統合されたDockerfileを作成

# --------------------------------------
# stage: PHP-FPM
# --------------------------------------
FROM php:8.4-fpm AS php-fpm

WORKDIR /srv

# Laravel のコードをコピー
COPY ./laravel-app /srv

RUN cp .env.example .env
RUN chown -R www-data:www-data storage database

RUN apt-get update && apt-get install -y \
    git \
    unzip \
    zip

COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN composer install

EXPOSE 9000

# --------------------------------------
# Nginx: Stage
# --------------------------------------
FROM nginx:alpine AS nginx

WORKDIR /usr/share/nginx/html

COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

として

docker build -t my-ecs-php-fpm -f ./srv/Dockerfile --target=php-fpm . --no-cache
docker build -t my-ecs-nginx -f ./srv/Dockerfile --target=nginx . 
docker-compose down; docker-compose up -d


これを解決するために、フロントエンドビルドステージを加える

# --------------------------------------
# frontend build stage
# --------------------------------------
# Stage 1: フロントエンドをビルド
FROM node:18-alpine AS frontend-builder

WORKDIR /laravel

# Laravelアプリをコピー
COPY ./laravel-app /laravel

# npmでビルド
RUN npm install && npm run build

# 生成されたpublicを/publicにcopy
RUN mkdir /public && cp -a public/* /public

# --------------------------------------
# stage: PHP-FPM
# --------------------------------------
# 略

これは最初のstageでnpmのinstallとビルドを行い、そのイメージの /publicにコピーしただけ。これだと動かないので、それぞれ、その成果物を自身にコピーする

最終Dockerfile

srv/Dockerfile
# --------------------------------------
# frontend build stage
# --------------------------------------
# Stage 1: フロントエンドをビルド
FROM node:18-alpine AS frontend-builder

WORKDIR /laravel

# Laravelアプリをコピー
COPY ./laravel-app /laravel

# npmでビルド
RUN npm install && npm run build

# 生成されたpublicを/publicにcopy
RUN mkdir /public && cp -a public/* /public

# --------------------------------------
# stage: PHP-FPM
# --------------------------------------
FROM php:8.4-fpm AS php-fpm

WORKDIR /srv

# Laravel のコードをコピー
COPY ./laravel-app /srv
# /public をコピー
COPY --from=frontend-builder /public /srv/public

RUN cp .env.example .env
RUN chown -R www-data:www-data storage database

RUN apt-get update && apt-get install -y \
    git \
    unzip \
    zip

COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN composer install

EXPOSE 9000

# --------------------------------------
# Nginx: Stage
# --------------------------------------
FROM nginx:alpine AS nginx

WORKDIR /usr/share/nginx/html

COPY ./srv/default.conf /etc/nginx/conf.d/default.conf
# /public をコピー
COPY --from=php-fpm /src/public/ /usr/share/nginx/html

EXPOSE 80

最終確認

必ず /login とかで行う事

ソース

https://github.com/catatsumuri/ecr-ecs-fargate-setup/tree/main/002-laravel-completed

結局

工数が多く1から組んでいると日が暮れてしまうので、ある程度のセットを自身のリポジトリーに用意して、laravelプロジェクトをくっつけてさくっとイメージをビルドできるようにしておいた方がよいかと思う。なお、ECSで2コンテナを通信する場合のホストはlocalhostになるので、これに対応をそのうち続編として書くつもりではある(需要ありそうならね)

Discussion