【初心者向け】MySQL環境のLaravelアプリをHerokuにデプロイするまで【Laravel8・Heroku・JawsDB】

## 対象読者
この記事は、私がローカルで作成済みのLaravelアプリをherokuでデプロイするまでの内容を整理したものです。
- ローカルでLaravelアプリを作った！
- 作ったサービスをherokuで公開したい！
- herokuで公開するにはどうすればいいの？

という方の参考になれば幸いです。

## 前提
### 環境
- macOS Monterey 12.3
- Homebrew 2.5.0
- Herokuでユーザー登録済み
- Heroku CLI導入済み

Heroku CLI未インストールの方は以下からどうぞ。  
[Heroku CLI | Heroku Dev Center](https://devcenter.heroku.com/ja/articles/heroku-cli#install-the-heroku-cli)

## 手順
**事前準備〜デプロイ**
1. [デプロイしたいLaravelアプリを準備する](#デプロイしたいlaravelアプリを準備する)  
1. [Gitリポジトリの初期化を行う](#gitリポジトリの初期化を行う)  
1. [Procfileを作成する](#procfileを作成する)  
1. [herokuにリポジトリを作成する](#herokuにリポジトリを作成する)  
1. [リモートのリポジトリとしてherokuのリポジトリにセットする](#リモートのリポジトリとしてherokuのリポジトリにセットする)  
1. [言語に対応したbuildpackを導入する](#言語に対応したbuildpackを導入する)  
1. [ローカルのLaravelアプリをherokuにデプロイ](#ローカルのlaravelアプリをherokuにデプロイ)  
1. [デプロイエラー対応](#デプロイエラー対応)  

**デプロイ〜動作確認**
1. [MySQLとの接続（JawsDBの導入）](#mysqlとの接続jawsdbの導入)  
1. [環境変数の設定](#環境変数の設定)  
1. [マイグレーションと動作確認](#マイグレーションと動作確認)  
1. [エラーレスポンス対応](#エラーレスポンス対応)  

## 事前準備からデプロイまで
### デプロイしたいLaravelアプリを準備する
まずはローカルに、herokuへデプロイしたいLaravelアプリを準備します。

前提として、この記事ではデプロイしたいアプリのディレクトリ構造は以下のようになっているものとします。  
※`example-app`は任意のアプリ名です。
```
example-app
├── app
├── bootstrap
〜中略〜
├── composer.json
〜後略〜
```

### Gitリポジトリの初期化を行う
ローカルでデプロイしたいLaravelアプリを準備したら、Gitリポジトリの初期化を行い、そのまま初期コミットを行います。
```
% cd example-app
% git init
	Initialized empty Git repository in **/example-app/.git/
% git add .
% git commit -m "new laravel project"
```

### Procfileを作成する
Procfileの詳細については下記参照  
[Procfile | Heroku Dev Center](https://devcenter.heroku.com/ja/articles/procfile)

ProckfileはHeorkuでのアプリ起動時に実行するコマンドを指定するファイルです。  
エディタで直接作成するか、内容は短いので下記コマンドで生成してもOKです（※拡張子はありません）
```example-app
% echo "web: vendor/bin/heroku-php-apache2 public/" > Procfile
```

Procfileを作成したらコミットしておきます。
```example-app
% git add .
% git commit -m "Procfile for Heroku"
```


### herokuにリポジトリを作成する
続いてpush先となるherokuに空のリポジトリを作成します。
```
% heroku create -a example-app
	Creating ⬢ example-app... done
https://example-app.herokuapp.com/ | https://git.heroku.com/example-app.git
```

### リモートのリポジトリとしてherokuのリポジトリにセットする
herokuにリポジトリを作成できたら、そのリポジトリをリモートとしてセットします。
```
% heroku git:remote -a example-app
	set git remote heroku to https://git.heroku.com/example-app.git
```

ちゃんとリモートにセットできているか、以下のコマンドで確認します。
```
% git remote -v
  //以下のように表示されればOK
  heroku  https://git.heroku.com/example-app.git (fetch)
  heroku  https://git.heroku.com/example-app.git (push)
```

### 言語に対応したbuildpackを導入する
Laravelアプリのデプロイにあたり、
以下のコマンドで[**node.js**](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-nodejs)と[**php**](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-php)のbuildpackを導入します。
```
% heroku buildpacks:set heroku/php
% heroku buildpacks:set heroku/nodejs
  //それぞれ以下のようなメッセージが出ればOK（以下はheroku/php導入時の例）
  Buildpack set. Next release on example-app will use heroku/php.
  Run git push heroku main to create a new release using this buildpack.
```

:::note info
buildpackの詳細については以下を参照
[Heroku buildpack | Heroku](https://jp.heroku.com/elements/buildpacks)
※buildpackについては私自身あまり理解できていません
現時点では、「Herokuでデプロイする際、言語やサービスの内容に応じて必要になるパッケージ」くらいのイメージです。
:::

### ローカルのLaravelアプリをherokuにデプロイ
buildpackの導入まで済んだら、いよいよアプリをherokuにpushします。
```
% git push heroku main
```

ここで成功した場合、以下のようなメッセージが出るはずです。
```
〜略〜
remote: Verifying deploy... done.
To https://git.heroku.com/example-app.git
〜略〜
```
herokuのActivityが以下のような状態になっていることを確認できればOK！
![succeeded.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1807203/13e08b7a-85f6-551c-d6e0-0a51f51d9809.png)

しかし何か問題があって失敗した場合は以下のようにpushが`reject`されます。
```
〜略〜
remote: Verifying deploy...
remote:
remote: !	Push rejected to example-app.
〜略〜
```
rejectの場合、Acrivityは以下のようになります。
![failed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1807203/039cb17c-819e-33fa-e7f6-7278da1a1ec2.png)


ここから先はherokuへのデプロイ時に遭遇したエラーとその対策について記述します。


### デプロイエラー対応
#### エラー１：No default language could be detected for this app.
**原因**：ビルドパックをインストールせずにpushした際に発生するエラーです。  
**解決策**：適切なビルドパックを導入することで解消します。

エラーの詳細は以下のとおり。
```
	remote: -----> Building on the Heroku-20 stack
	remote: -----> Determining which buildpack to use for this app
  //デフォルトの言語が設定されていません
	remote:  !     No default language could be detected for this app. 
  //ヒント：この問題は、Herokuがこのアプリに使うべきbuildpackを自動的に検出できない時に発生します
	remote: 			HINT: This occurs when Heroku cannot detect the buildpack to use for this application automatically. 
	remote: 			See https://devcenter.heroku.com/articles/buildpacks
	remote:
	remote:  !     Push failed
	remote: Verifying deploy...
	remote:
	remote: !	Push rejected to example-app.
```

エラーメッセージで原因がちゃんとわかるのがありがたいですね。  
メッセージ内で**HINT**として記載されているURLはこちらです。  
[Buildpacks | Heroku Dev Center](https://devcenter.heroku.com/articles/buildpacks)


#### エラー２：Application not supported by this buildpack!
**原因**：不適切なbuildpackを導入していたり、ディレクトリ構成をミスっていると発生します。  
私の場合はディレクトリ構成ミスで発生しました。  

**解決策**：こちらは適切なbuildpackを導入するか、プッシュするディレクトリの構成を見直せば解決します。


```
// このビルドパックではサポートされていないアプリです！
ERROR: Application not supported by this buildpack!
	remote:  !
  // phpのビルドパックをセットしてますがphpのアプリじゃないと思います。
	remote:  !     The 'heroku/php' buildpack is set on this application, but was
	remote:  !     unable to detect a PHP codebase. 
	remote:  !
  // phpのアプリだったらcomposer.jsonがルートディレクトリにある構造か、古いものならindex.phpが必要です。
	remote:  !     A PHP app on Heroku requires a 'composer.json' at the root of
	remote:  !     the directory structure, or an 'index.php' for legacy behavior.
	remote:  !
  // もしphpのアプリとしてデプロイしたいなら、ディレクトリのトップレベルを確認してください。
	remote:  !     If you are trying to deploy a PHP application, ensure that one
	remote:  !     of these files is present at the top level directory. 
	remote:  !
  // もし違う言語で書かれたアプリなら、適切なビルドパックを使ってください。
	remote:  !     If you are trying to deploy an application written in another
	remote:  !     language, you need to change the list of buildpacks set on your
	remote:  !     Heroku app using the 'heroku buildpacks' command.
```

#### エラー３：WARNING: A post-autoload-dump script terminated with an error
**原因**：依存関係のインストール失敗によるエラーです。
私の場合はその時点でcomposer.lockに記載されたPHPのバージョンがherokuに対応していなかったため、古いバージョンを使おうとしていたことが原因と考えました。

**解決策**：以下の順番で作業を行い解決。
1. composer.lockに記載されたphpのバージョンを上げるため`composer require php:"バージョン指定"`を実行したもののエラー継続
2. `composer update`を実行するもエラー継続（※`composer update`を使うときは要注意。せっかくlockファイルで固定しているバージョンを上書きしてしまう危険があるため。詳細は[こちらの記事](https://tech-gineer.com/environment/104/#outline__3_3)をどうぞ。）
3. 一度composer.lockを削除して、改めて`composer install`を実行し、再度pushしたところ解決。

```
  // post-autoload-dumpスクリプトがエラーにより終了しました。
	remote:  !     WARNING: A post-autoload-dump script terminated with an error 
	remote: 
  // 依存関係のインストールの失敗です！
	remote:  !     ERROR: Dependency installation failed! 
	remote:  !     
  // 'composer install'プロセスが失敗しました。原因はおそらく'composer.json'における、ダウンロードか、パッケージのインストールか、事前/事後インストールのフックによるものです
	remote:  !     The 'composer install' process failed with an error. The cause
	remote:  !     may be the download or installation of packages, or a pre- or
	remote:  !     post-install hook (e.g. a 'post-install-cmd' item in 'scripts')
	remote:  !     in your 'composer.json'. 
	remote:  !     
  // 典型的なエラーケースとしては、コードが時代遅れ（＝古い？）であることや、不足している部分があること、外部とのコネクション構築のタイムアウトやメモリの限界があります。
	remote:  !     Typical error cases are out-of-date or missing parts of code,
	remote:  !     timeouts when making external connections, or memory limits.
```

## デプロイから動作確認まで
ここから先はpushに成功し、サービスがデプロイできた状態から行う作業になります。
![succeeded.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1807203/13e08b7a-85f6-551c-d6e0-0a51f51d9809.png)

流れとしては、以下のようになります。
1. MySQLとの接続（JawsDBの導入）  
1. 環境変数の設定  
1. マイグレーション  
1. 動作確認  


### MySQLとの接続（JawsDBの導入）
HerokuでMySQLを使用するにあたっては、herokuのプラグインからアドオンを導入する必要があります。  
アドオンにはClearDBとJawsDBの２種類がありますが、この記事ではJawsDBを扱います。  
ここはご自身のデプロイしたいサービスで活用しているMySQLのバージョンに合わせて検討してみてください。  
（ClearDBの対応は5.6まで。JawsDBは8.0も対応）

#### JawsDBの導入
今回導入するJawsDBは、Herokuアプリで使用できるMySQLサーバを提供してくれるアドオンです。  
[JawsDB MySQL | Heroku Dev Center](https://devcenter.heroku.com/articles/jawsdb)


今回は無料で使える**Kitefinプラン**を指定して導入します。  
JawsDBのプランについては[JawsDB MySQL - Add-ons - Heroku Elements](https://elements.heroku.com/addons/jawsdb#pricing)をご覧ください。


なお、Kitefinプランは無料であるもののクレカ情報の登録が必要なため（登録しないとエラーになる）、事前に登録します。  
[アカウントの確認 | Heroku Dev Center](https://devcenter.heroku.com/ja/articles/account-verification#how-to-verify-your-heroku-account)  

手順は以下の通り。
1.  Heroku Dashboardの[`Account Settings`](https://dashboard.heroku.com/account)(アカウント設定) に移動
2.  **`Billing`** (請求) タブをクリック
3.  **`Add Credit Card`** (クレジットカードの追加) をクリック


クレカの登録ができたら、下記のコマンドにより、**Kitefinプラン**の**MySQL8.0**を導入できます。
```
%  heroku addons:create jawsdb:kitefin --version=8.0
	Creating jawsdb:kitefin on ⬢ example-app... free
	Database is being provisioned. Your config_var will be set automatically once available.
	Created * as JAWSDB_URL
	Use heroku addons:docs jawsdb to view documentation
```

うまくいったら以下のコマンドでアドオンがちゃんと追加されているか確認します。
```
% heroku addons 

Add-on                             Plan     Price  State  
─────────────────────────────────  ───────  ─────  ───────
jawsdb (*)                         kitefin  free   created
 └─ as JAWSDB

The table above shows add-ons and the attachments to the current app (example-app) or other apps.
```

※ちなみにクレカ未登録でJawsDBを導入しようとすると以下のように怒られます。
```
%  heroku addons:create jawsdb:kitefin --version=8.0
	Creating jawsdb:kitefin on ⬢ example-app... !
	 ▸    Please verify your account to install this add-on plan (please enter a credit card) For more information, see https://devcenter.heroku.com/categories/billing Verify now at
	 ▸    https://heroku.com/verify
```

### 環境変数の設定
JawsDBの導入ができたらLaravelの環境変数をherokuで設定します。  
Laravelの環境変数についてはこちらをどうぞ。（記事のLaravelは5.7ですが、非常に参考になりました。）  
[保存版！Laravelの.envでできること大全 – console dot log](https://blog.capilano-fw.com/?p=1382#:~:text=APP_URL%EF%BC%9A%20%E3%82%B5%E3%82%A4%E3%83%88%E3%81%AEURL,-APP_URL%20%E3%81%AF%E4%BB%A5%E4%B8%8B)  

herokuの環境変数は`heroku config`コマンドで行います。  
設定する環境変数は以下のとおりです。  
- APP_KEY
- APP_URL
- JAWSDB_URL
- DB_CONNECTION
- DB_HOST
- DB_USERNAME
- DB_PASSWORD
- DB_PORT
- DB_DATABASE

#### APP_**について
まずはAPP_KEYを出力し、その内容をコピペでherokuの環境変数にセットします。
```
// APP_KEYの出力
% heroku run "php artisan key:generate --show"
	Running php artisan key:generate --show on ⬢ example-app... up, run.3690 (Free)
  // 以下をbase64:も含めてコピー
	base64:**********************

// APP_KEYをherokuの環境変数にセット（コピーした内容を"APP_KEY="に続けてペーストする）
% heroku config:set APP_KEY=base64:**********************
  Setting APP_KEY and restarting ⬢ example-app... done, v
  APP_KEY: base64:**********************
```

続いてAPP_URLをセットします。
APP_URLはアプリの公開URLでheroku上で確認できるほか、`heroku info --app example-app`とコマンド入力するとWeb URLとして表示できます。
```
% heroku config:set APP_URL=https://example-app.herokuapp.com/
```
参考
[Heroku URLの確認　一覧表示 - Qiita](https://qiita.com/onthe6ks/items/9b273b201dfd1da81d8c)

#### JAWSDB_URL及びDB_**について
JAWSDB_URLの中にDB_\*\*の環境変数が含まれているため、  
**JAWSDB_URLの値を確認→それぞれの環境変数にセット**  
という流れで設定します。

```
// JAWSDB_URLの値を確認
% heroku config:get JAWSDB_URL
> mysql://◯◯◯◯:△△△△@□□□□:####/××××

// 上記で表示したJAWSDB_URLは以下のような構造になっている
// mysql://"DB_USERNAME":"DB_PASSWORD"@"DB_HOST":"DB_PORT"/"DB_DATABASE"

// それぞれの環境変数をセットする
% heroku config:set DB_USERNAME=◯◯◯◯
% heroku config:set DB_PASSWORD=△△△△
% heroku config:set DB_HOST=□□□□
% heroku config:set DB_PORT=####
% heroku config:set DB_DATABASE=××××
```

登録した環境変数は`heroku config`で確認できます。

なお、DB周りの環境変数はherokuのダッシュボードからJawsDBにアクセスすることでも確認可能です。  
アクセス手順は以下のとおり。  
**Overviewタブ->Installed add-ons->JawsDB MySQL**

### マイグレーションと動作確認
無事に環境変数の設定が完了したら以下のコマンドを実行し、マイグレーションを行います。  
途中で本当にコマンドを実行していいか確認を求められるので`yes`と入力します。
```
% heroku run php artisan migrate              
Running php artisan migrate on ⬢ example-app... up, run.9658 (Free)
**************************************
*     Application In Production!     *
**************************************

 Do you really wish to run this command? (yes/no) [no]:
 > yes

Migration table created successfully.
Migrating: ...
〜以下略〜
```

ここまできたら`heroku open`のコマンドを実行しサービスを開いてみます。  
うまくいけばちゃんとサービスが表示されます！お疲れ様でした！


### エラーレスポンス対応
#### 500 Server Error：Trait "ライブラリ名" not found
**原因**：開発中は動いていたライブラリが見つからないと言われた場合、composer.jsonの記述が誤っているものと思われます。  
**解決策**：composer.jsonの**require**に見つからないといわれているライブラリがあるかどうかを確認し、無ければ`composer require "ライブラリ"`で導入することで解決します。  

composer.jsonには、**require-dev**という欄がありますが、ここは開発環境用にインストールするプログラムを記述する場所で、ここに記述されたプログラムは本番環境ではインストールされません。（この辺りしっかりできている仕組みだなあと感心しました）  
では本番環境でインストールされるプログラムはというと**require**という欄に記述されます。  

私の場合は、ソート機能を導入するためにインストールした**kyslik/column-sortable**が見つからない状況でした。  
**kyslik/column-sortable**は、**require-dev**に記述されていたため、`composer require kyslik/column-sortable`により**require**に記述を移し、変更内容をコミットしてpushすることで解決しました。

## 終わりに
自分にとって初めてのデプロイだったため、「簡単にできる！」と評判のherokuを使いましたが、そんなに甘くはなかったです 笑  
ただデプロイに挑戦したことで環境変数や本番/開発環境についての理解がだいぶ進みました（多分）  
この記事内で行ったエラー対応が誰かの役に立てば幸いです。