Dockerで構築した環境を基に、Herokuへデプロイする(Nginx, Laravel,VueJS,PostgresSQL)

#目的#
Dockerで構築したローカルの開発環境を基に、
アプリケーションをHerokuへデプロイしたい。

Herokuの機能を使用して、
GitHubのリモートリポジトリへPushしたら自動でHerokuのデプロイが走るようにしたい。

#参考#
https://qiita.com/3no3_tw/items/25cb7c0c09d51b56eb5a

https://qiita.com/minato-naka/items/2d2def4d66ec88dc3ca2

https://qiita.com/sutara79/items/a173b969474d9f5afe1b

上記の記事を参考にいたしました。

#環境#
以下の環境をDockerを使ってローカル上に構築しています。

|サービス|名前|バージョン|
|:-:|:-:|:-:|
|Webサーバー|nginx|1.15.6|
|フロントエンドFW|VueJS|vue@2.6.14|
|バックエンドFW|Laravel|7.30.5 (php:7.2-fpm)|
|データベース|PostgreSQL|latest(psql (PostgreSQL) 14.1 (Debian 14.1-1.pgdg110+1))]|
|データベースGUI|PG Admin |4|

#事前準備#
・Herokuへの登録
・Heroku CLIのインストール
・GitHubへの登録

#設定ファイル#
https://github.com/roughstorm/dnlvp

に今回使用する設定ファイルを置いています。

#開発環境構築(Nginx, Laravel, PostgresSQL)#
```bash:powershell
git clone https://github.com/roughstorm/dnlvp
cd dnlvp
docker-compose up --build -d
docker-compose exec app bash
```
リポジトリからクローンした設定ファイルを基にDocker-composeでDocker上に環境構築し、appコンテナ内に入る。

:::note info
上記の設定ファイルを使用する場合は、以下の事項に注意してください。

ローカルでのDockerコマンド実行前に、`docker-compose.yml`内の`pgadmin`の`environment`の`PGADMIN_DEFAULT_EMAIL`のメールアドレスを適宜変更してください。

`docker/web/default.conf`内にてrootを`root /var/www/html/[dnlvp]/public;`と指定しています。
作成するLaravelのプロジェクト名(フォルダ名)に合わせて[dnlvp]の部分を変更してください。

:::


```bash:app
composer create-project --prefer-dist laravel/laravel dnlvp
cd dnlvp/
chmod -R 777 storage
```
composerコマンドで新しいLaravelプロジェクトを作成し、
storageフォルダに権限を付与(log出力のエラーが出るため）

```conf:.env
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=sample
DB_USERNAME=user
DB_PASSWORD=password
```
Laravelプロジェクト作成時にできる環境設定ファイル(`.env`)にPostgresSQL用の環境変数をセットする。

```bash:app
php artisan migrate
```
`php artisan migrate`で、DBのマイグレーションを行っておく。


#VueJSのインストール#
```bash:app
composer require laravel/ui
php artisan ui vue
npm install
npm install -D vue
npm install --save vue-router
npm list vue
npm run dev
npm audit fix
npm run watch
```
`composer require laravel/ui`のコマンドでLaravelでVue(Reactも)を使うためのライブラリをインストールします。
`php artisan ui vue`コマンドでvue用のスカフォールドを設定します。

`npm`コマンドnodejsのコマンドです。
`npm install`コマンドについては、下記のサイトが参考になりました。

https://www.codegrid.net/articles/2020-npm-install-1/#toc-1

`npm list vue`コマンドで、バージョンが返ってくればvueがインストールされています。

`npm run watch`コマンドまで実行したら、動作を確認します。

#ローカルでの動作確認#
まず、動作確認用に以下のファイルを編集/作成します。
 dnlvp/routes/web.php
 dnlvp/resouces/views/app.blade.php

```php:dnlvp/routes/web.php
<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/{any}', function() {
    return view('app');
})->where('any', '.*');
```

```php:dnlvp/resouces/views/app.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Vue Laravel SPA') }}</title>

    <!-- Styles -->
    <link href="{{ mix('/css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
<example-component></example-component>
</div>
<!-- Scripts -->
<script src="{{ mix('/js/app.js') }}" defer></script>
</body>
</html>
```
上記のファイルを更新/作成した状態で、localhost:8000にアクセスし、以下の画面のように動作が確認できると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/eb5ca049-077f-e6a8-93ed-24fdc1ac2e37.png)

#.gitignoreの設定#
コンパイル済みファイルである、`/public/js`と`/public/css`はgit管理する必要がないのでgitignoreに追記しておきます。
これらのファイルは、Herokuではデプロイ時にまた作成されるようです。

```conf:.gitignore
/public/js
/public/css
```

#Herokuへのデプロイ#

下記コマンドを実行する前に、カレントディレクトリをLaravelのプロジェクトフォルダに移動しておいてください。

#Herokuアプリの作成#
```bash:powershell
heroku login
heroku create dnlvp
```
`heroku login`でHerokuにログインします。ブラウザが開いて認証します。
`heroku create`で新しいHerokuアプリを作成します。 `dnlvp`がアプリ名になります。既に同じ名前のアプリがあると作成できません。（testとかはすでにとられてます。)

#GitHubリポジトリの準備#
今回は、Herokuの機能を使用して、GitHub上のリモートリポジトリにPushしたらHerokuにもデプロイが走るようにしたいので、まずGitHubにHerokuアプリ用のリポジトリを作成します。

今回は、https://github.com/roughstorm/dnlvponheroku.gitというリポジトリを作成しました。

https://github.com/roughstorm/dnlvponheroku.git

HerokuとGitHubで連携をする場合は、リポジトリが空だと連携出来ないと表示されるので、まず先にリポジトリ内にファイルをいれます。

```
echo "It's a practice for deploy on Heroku." >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/roughstorm/dnlvponheroku.git
git push -u origin main
```
これらのコマンドは、カレントディレクトリをLaravelのプロジェクトフォルダに移動してから実行してください。（.envなどがあるフォルダ)


#HerokuとGitHubを連携させて自動デプロイの設定#
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/f108d004-c9a2-a091-1f62-e6ff7e0725f5.png)
https://jp.heroku.com にログインし、作成したプロジェクトを選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/bee0994e-1bf7-aa71-9ece-44cb47bba02b.png)
Deployをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/9163a7d1-0a67-9d19-cd83-bb5fbfcbb6d6.png)
Deployment methodでGitHubを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/7b1a599d-acc1-bcae-f212-1bb52ed3dfd8.png)
Github上のリポジトリを検索するテキストボックスが出るので、自身の設定したリポジトリ名を検索して、適用したいリポジトリを選んでConnectを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/de5fc652-4aed-783e-4d1d-d5eb60bf7ed5.png)
まだこの状態ではPushしただけで自動でデプロイが走るようにはなっていません。
HerokuとリポジトリのConnectが完了すると、Automatic deploysという項目ができて、Enable Automatic Deploysというボタンが出ますので、こちらを押すことによって自動デプロイの設定が完了します。


#Heroku側のBuildpacks(PHPとNodeJS）の設定#
Heroku側での設定をしていきます。
なお、下記Herokuコマンドは `-a` で自身のアプリ名を指定しないと動きません。

```powershell:powershell
heroku buildpacks:set heroku/php -a dnlvp
heroku buildpacks:add heroku/nodejs -a dnlvp
```
buildpacks(アプリがデプロイされる際に実行されるスクリプト)の設定をします。
LaravelとVueを使うので、phpとnodejsをセットします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/97dc6d99-c251-a4e3-8a65-29af1586ec9a.png)
ちなみに、　上画像のように　https://jp.heroku.com　
にログインし、アプリを選択した後、Settingsのタブからもbuildpacksの設定、確認を行うことができます。

#Heroku側のPostgresSQLの設定#
```powershell:powershell
heroku addons:create heroku-postgresql:hobby-dev -a dnlvp
```
addonにPostgresSQLを追加します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/6fe4fc15-53ed-a98c-27fa-58cc443ac8a7.png)
こちらも、https://jp.heroku.com
からログインし、アプリを選択した後、Resourcesのタブから設定や確認ができます。


```powershell:powershell
heroku config:get DATABASE_URL -a dnlvp
```

addonにPostgreSQLを追加すると、自動で環境変数にDATABASE_URLが追加されるので、そちらを取得し、Herokuへ設定していきます。
`heroku config:get DATABASE_URL`を実行すると、以下の形式で返ってきますので、これらを基に対応する値をHerokuへ設定していきます。
postgres://<ユーザ名>:<パスワード>@<ホスト>:5432/<DB名>

```powershell:powershell
heroku config:set DB_CONNECTION=pgsql -a dnlvp
heroku config:set DB_HOST=<ホスト> -a dnlvp
heroku config:set DB_DATABASE=<DB名> -a dnlvp
heroku config:set DB_USERNAME=<ユーザ名> -a dnlvp
heroku config:set DB_PASSWORD=<パスワード> -a dnlvp
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/8b26e697-81ed-5dfb-8c5a-78a5910fe540.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/169846b1-78de-f782-e081-db8b4f67883f.png)

こちらも、https://jp.heroku.com
からログインし、アプリを選択した後、SettingsのタブのConfig Varsから設定や確認ができます。

#Laravelの環境変数の設定#
```powershell:powershell
heroku config:set APP_ENV=production -a dnlvp
heroku config:set APP_DEBUG=false -a dnlvp
heroku config:set APP_URL=https://dnlvp.herokuapp.com/ -a dnlvp
```
Laravelで使用するAPP_ENVなどの環境変数を設定します。
`APP_URL=https://dnlvp.herokuapp.com/`については、自身のアプリのURLを指定してください。



#Procfile, nginx.confの追加# 
Laravelプロジェクトのフォルダに、下記のファイルを新規作成します。
 `Procfile`
 `nginx.conf`

```conf:Procfile
web: vendor/bin/heroku-php-nginx -C nginx.conf public/
```
`Procfile`はアプリケーションがどのプロセスを使うのか宣言するためのファイルだそうです。

`web: vendor/bin/heroku-php-nginx` によってnginxを使用することを指定し、
`-C nginx.conf`　で、同じフォルダ内にある nginx.confファイルを参照することを指定、
`public/`　でドキュメントルートの指定をしています。

また、`Procfile`はファイル名が決まっているので大文字(PROCFILE)とか小文字(procfile)だと動きませんでした。


```conf:nginx.conf
location / {
    try_files $uri @rewriteapp;
}

location @rewriteapp {
    rewrite ^(.*)$ /index.php$1 last;
}
```

`location / {~ ~}`にて、アクセスしたURLのファイルがあるかどうかを確認し、あればそのファイルを返し、なければ@rewriteappロケーションに行きます。

`location @rewriteapp{~ ~}`にて、送られてきたURI応じて、index.php[正規表現に一致する部部分]として書き換え、lastがあるので、`location / {~ ~}`の処理から、また一致するかどうかを確認していく、と思います。

今回デプロイするHerokuのアプリでは、アプリのURLにアクセスすると,`Procfile`に設定した`public/`へ行き、nginx.confの処理によって`public/index.php`が戻ってくることによって、アプリへアクセスが出来るということになると思います。

下記の記事を参考にいたしました。

https://www.skyarch.net/blog/?p=7088

https://qiita.com/kaikusakari/items/cc5955a57b74d5937fd8

#package.json, composer.jsonの編集#
下記のファイルを編集します。
 `package.json`
 `composer.json`

```conf:package.json
 "heroku-postbuild": "npm run prod"
```
`package.json`内の`"scripts"`要素の中に、`"heroku-postbuild": "npm run prod"`を追加します。
これをいれないと、Heroku上でnpm run buildがされないので、vueが使えません。

`"scripts"`要素内に追加する際に、最後の行に入れる場合は、上行の改行の前に`,`を入れないと、ファイルを読んだ際にエラーが出ます。

```json:composer.json
    "require": {
        "php": "^7.2.5|^8.0",
        "ext-intl": "*",
        "fideloper/proxy": "^4.4",
        "fruitcake/laravel-cors": "^2.0",
        "guzzlehttp/guzzle": "^6.3.1|^7.0.1",
        "laravel/framework": "^7.29",
        "laravel/tinker": "^2.5",
        "laravel/ui": "^2.5"
    },
```
`composer.json`内の`require`要素内に`"ext-intl": "*",`を追加します。
intl(国際化用拡張モジュール)を使用できるようにする為に必要です。
PHPで国際化用に使うライブラリの様です。
これらが具体的にどんな動作をするのかまではまだわかっていません・・すいません。

#https強制化#

https://zenn.dev/karaageeeee/articles/b6f201d61a2f81

こちらの記事を参考にいたしました。

```php:app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        // 本番環境(Heroku)でhttpsを強制する
        if (\App::environment('production')) {
            \URL::forceScheme('https');
        }
    }
}

```
まず、ローカル環境の`app/Providers/AppServiceProvider.php`を上記の様に変更します。

```powershell:powershell
docker-compose exec app bash
cd dnlvp/
php artisan make:middleware ForceHttpToHttps
```

Laravelのミドルウェアを作るため、コンテナ内に入り、
Laravelプロジェクトのフォルダで`php artisan make:middleware ForceHttpToHttps`を実行します。

```php:app/Http/Middleware/ForceHttpToHttps.php
<?php

namespace App\Http\Middleware;

use Closure;

class ForceHttpToHttps
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (\App::environment(['staging', 'production']) && $_SERVER["HTTP_X_FORWARDED_PROTO"] != 'https') {
            return redirect()->secure($request->getRequestUri());
        }
        return $next($request);
    }
}
```
`php artisan make:Middleware`で自動的に`ForceHttpToHttps.php`が作成されますので、
`app/Http/Middleware/ForceHttpToHttps.php`を上記の様に書き換えます。

```php:app/Http/Middleware/Kernel.php
(省略)
protected $middleware = [
(省略)　
     \App\Http\Middleware\ForceHttpToHttps::class, // 追加
    ];
(省略)
```
`app/Http/Middleware/Kernel.php`に上記を追加します。

また、これらの変更を加えた時点でローカル環境に異常が発生していないか確認をしたほうがよいと思います。

#デプロイ#
```bash:powershell
git add .
git commit -m "initial commit"
git branch -M main
git push -u origin main
```
Laravelプロジェクトのフォルダで一度、上記を実行します。

これで一度Heroku上でデプロイが走ると思いますが、
設定していない項目がありますので、まだアプリは動きません。

#APP_KEYの設定#
```powershell:powershell
heroku run php artisan key:generate --show -a dnlvp
heroku config:set APP_KEY='{ここに上記で生成されたAPP_KEY(base64から始まる文字列)をコピペ}' -a dnlvp
```
上記コマンドでAPP_KEYの設定をします。
`APP_KEY=$(php artisan key:generate --show)`で設定している記事を多くみかけましたが、私の環境からだと何度やってもAPP_KEYに空が設定されてしまうので、この様にしております。

#DBのマイグレーション・シーディング#
```powershell:powershell
heroku run php artisan migrate --seed -a dnlvp
```
上記を実行します。

```powershell:powershell
**************************************
*     Application In Production!     *
**************************************

 Do you really wish to run this command? (yes/no) [no]:
 >
```
既にアプリケーションが動いている状態ですので、ほんとにマイグレーションしていいの？と聞かれますので、Yesでマイグレーションを実行します。


#動作確認#
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/f1a6cdb1-3612-c18f-f0af-55f44ffcc233.png)
自身のアプリのURLにアクセスし、動作を確認します。
Heroku上に構築された環境で、Vueのコンポーネントが表示されました。


#おわりに#
突貫な感じで恐縮ですが、開発から本番までの環境を作ることができてよかったです。

技術から別の技術へいろいろと派生していき、これを調べたらあれも調べないと、
みたいな感じで調査に時間がかかってしまうので、勉強不足を痛感しました。

技術的に拙い部分が多くあって怖いですが、
とりあえず環境ができたので実装のほうをやっていきたいと思っています。

内容について、やってはいけない感じのことをやっていたり、
こうしたほうがよいなどのご助言がありましたら、お教え頂けますと幸いです。

#追記2021/11/23#
私の環境だとこれで構築したDocker内の環境では、
npm run watch, npm run watch -pollともになぜか正常に動作しません。
ファイルの変更をキャッチして勝手にビルドしてくれないのです。なぜだい。

たぶん、webpack.mix.jsあたりに何かありそうな気がするんですが、頭が働いてくれませんでした。
なので、jsやvueを変えたら都度npm run devしてください。悲しみ。