Docker環境のFlaskとVueをHerokuにデプロイする【備忘録】

#ディレクトリ構成

```:ディレクトリ構成
.
├─ dist
|  └─ static
│  └─ index.html
└─ frontend
|  └─ node_modules
|　└─ src
|  └─ package.json
|  └─ Dockerfile
|  └─ etc...
└─ app.py
└─ docker-compose.yml
└─ Dockerfile
└─ requirements.txt
└─ .gitignore
```

#Docker環境を整える
docker-composeとDockerfileをルートディレクトリに作成。

```bash:bash
$ git init
$ touch {docker-compose.yml,Dockerfile}
```

```yml:docker-compose.yml
version: "3"
services:
  frontend:
    container_name: frontend
    build:
      context: .
      dockerfile: ./frontend/Dockerfile
    volumes:
      - ./:/app
    ports:
      - "8080:8080"
    tty: true
    stdin_open: true

  backend:
    container_name: backend
    build:
      context: .
      dockerfile: ./Dockerfile
    volumes:
      - ./:/app
    ports:
      - "5000:5000"
    stdin_open: true
    tty: true
```

```Dockerfile:Dockerfile
FROM python:3.8

WORKDIR /app

COPY . .

RUN pip3 install flask

EXPOSE 5000

CMD python app.py
```
ここで起動コマンド`CMD python app.py`を記述していないとHerokuにデプロイした時にエラーになる

#バックエンド準備
ルートディレクトリにPythonファイルを作成して、以下のように記述。
render_templateはVueの`npm run build`で書き出されたものを相対パスで指定する。

```python:app.py
from flask import Flask, render_template
import os

app = Flask(__name__, static_folder='dist/static', template_folder='dist')

# ルートの設定
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):
    return render_template("index.html")

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port, debug=True)
```

#フロントエンド準備
frontendフォルダにDockerfileを作成
VueCLIをインストールしておく

```Dockerfile:Dockerfile
FROM node:lts-alpine

WORKDIR /app

RUN npm install -g @vue/cli && \
    npm install -g @vue/cli-init

ENV HOST 0.0.0.0
```

##コンテナを起動
```terminal:bash
$ docker-compose up -d --build
```

nodeコンテナに入る。

```terminal:bash
$ docker-compose exec frontend sh
```
nodeコンテナに入ったらVueCLIをたたく。
マニュアルを選択してvue-routerをインストールして必要な環境は適時導入しておく

```terminal:frontend/sh
$ vue create frontend

Vue CLI v4.5.13
? Please pick a preset:
  Default ([Vue 2] babel, eslint)
  Default (Vue 3) ([Vue 3] babel, eslint)
❯ Manually select features
```

```terminal:frontend/sh
Vue CLI v4.5.13
? Please pick a preset: Manually select features
? Check the features needed for your project:
 ◉ Choose Vue version
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
❯◉ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing
```

vueフォルダが生成されるので中身をすべてfrontendの中に移す。

```:ディレクトリ構成
└─ frontend
   └─ vue
   |  ├─ node_modules
　 |  ├─ src
   |  ├─ package.json
   |  └─ etc...
   └─ Dockerfile

   ||
   \/

└─ frontend
   ├─ node_modules
 　├─ src
   ├─ package.json
   ├─ Dockerfile
   └─ etc...
```

##vue.config.jsを作成
staticフォルダが生成されるようにする。

```terminal:frontend/sh
$ touch vue.config.js
```

```javascript:vue.config.js
module.exports = {
  assetsDir: "static",
};
```

##package.jsonを編集

```diff:package.json
"scripts": {
    "serve": "vue-cli-service serve",
-    "build": "vue-cli-service build",
+    "build": "vue-cli-service build --dest ../dist",
  },
```

##.gitignoreを編集
今回はDockerコンテナをHerokuにPushするので必要ないが、Gitでデプロイする場合を考慮して一応.gitignoreから`/dist`を除外しておく。

```diff:.gitignore
.DS_Store
node_modules
- /dist

# local env files
.env.local
.env.*.local
```
##faviconをstaticに移動

```:ディレクトリ構成
frontend
└─ public
   ├─ favicon.ico
   └─ index.html

   ||
   \/

frontend
└─ public
   ├─ static
   |   └─ favicon.ico
   └─ index.html
```

## ローカルで確認してみる

```terminal:frontend/sh
$ cd frontend
$ npm run serve
```

localhost:8080をブラウザで開いてVueの初期画面が出れば成功

![screenshot-tranquil-forest-19074.herokuapp.com-2021.09.19-18_42_59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/975119/6c59ec88-22be-bf8a-f3d7-00211a774b56.png)

##ルートディレクトリにdistを作成
```terminal:frontend/sh
$ cd frontend
$ npm run build
```

```:ディレクトリ構成
.
├─ dist              // 生成
├─ frontend
├─ app.py
├─ docker-compose.yml
├─ Dockerfile
├─ requirements.txt
└─ .gitignore
```

#requirements.txtの書き出し
requirements.txtを作成していないとHerokuでstackを読み込んでくれなかったためルートディレクトリに書き出しておく

##コンテナに入る

```bash:bash
$ docker-compose exec backend bash 
```
入ったら下記を実行

```python:backend/bash
pip freeze > requirements.txt
```

#Herokuにデプロイ

CLIを使ってHerokuにログインする

```bash:bash
$ heroku login
```
アプリをHeroku上に作成

```bash:bash
$ heroku create
```

Gitでもデプロイすることができるが今回はDockerでデプロイする

```bash:bash
$ heroku container:login
```

コンテナをHerokuにプッシュ

```bash:bash
$　heroku container:push web
```

アプリをデプロイ

```bash:bash
$ heroku container:release web
```

#ハマったところ
次スタックしないための備忘録
##Dockerコンテナのアクセス権限
Docker-composeでVolumeをルートにせずにディレクトリ指定しているとバックエンドからアクセスできなかった。

```yml:変更前
    volumes:
      - frontend:/app
```

```yml:変更後
    volumes:
      - ./:/app
```

##ディレクトリ構成
Herokuにデプロイの関係上Dockerfileはルートディレクトリに置いておかないとうまくプッシュできなかった。
当初はディレクトリ構成をフロントとバックエンドで分けていたがバックエンドのファイル群をルートに移すことで解決した。

```:変更前
.
├─ dist
├─ frontend
├─ backend
└─ docker-compose.yml
```

```:変更後
.
├─ dist
├─ frontend
├─ app.py
├─ docker-compose.yml
├─ Dockerfile
├─ requirements.txt
└─ .gitignore
```

##Vue構築時の注意
自分の環境だと`vue init webpack アプリ名`でVueを構築するとHrokuにデプロイした時にindex.htmlが見つからないエラーがでた。

```bash:log
jinja2.exceptions.TemplateNotFound: index.html
```

試行錯誤の末`vue create アプリ名`でVueプロジェクトを作成するとうまくいった。

##起動コマンド
Herokuにデプロイするときは起動コマンドを記述してあげる必要がある。
色々ありそうだが今回はbackendのDockerfileにCMDで記述した。

```docker:Dockerfile
FROM python:3.8

WORKDIR /app

COPY . .

RUN pip3 install flask

EXPOSE 5000

CMD python app.py
```

#番外編
Gitでデプロイする場合はrequirements.txtがルートディレクトリにないとHerokuが何の言語を使っているのか判断できない。
さらに.gitignoreからdistを削除しておかないとdistを読み込んでくれないためindex.htmlを参照しない。