TL;DR
- Heroku に Docker イメージをデプロイします
- SPA を同一オリジンで Django から配信するアーキテクチャです
- Docker 関連以外の話はしません (静的ファイル配信、DB 等)
Heroku には Docker イメージもデプロイできる
Heroku といえば、Git からデプロイのイメージが強かったんですが、Docker イメージでのデプロイにも対応しているらしいです
Deploying with Docker | Heroku Dev Center
によると、
- 手元で Docker イメージをビルドして、Heroku に投げる
heroku.yml
を使って、リポジトリからイメージをビルドする
の 2 つがあります
この記事では、前者を使いますが、CI/CD を絡ませるなら、手元でビルドするのではなく heroku.yml
にしたがって Heroku 上でビルドできるので後者のが良さそうな印象でした
どちらも単一のイメージから建てるものですが、Heroku にはもともと Heroku Postgres があり、Docker イメージならマルチステージビルドも行えるので
- フロント: SPA / SSG
- バック: API サーバー
- RDB: Heroku Postgres
みたいなアーキテクチャなら対応できます
逆に、API サーバーも建てるけどフロントでは SSR したいとかは無理なはずなので、別の形を考える必要があると思います
SPA on Django のイメージを作る
Heroku では、
Using Multiple Buildpacks for an App | Heroku Dev Center
辺りを使うことで、 ビルドに Node.js と Python を使う、みたいなことはできるんですが、
プロジェクトルートからデーモン用のビルドパックを自動認識するので
- Django Project (
Python Runtime
)- …
- frontend (
Node Runtime
)- …
みたいなディレクトリ構成じゃないと(たぶん)ダメです
Docker のビルドならこの辺の縛りがないのでとても良きです
とりえあず
- backend (
Django Application
) - frontend (
Node Application
) - …
みたいなディレクトリ構成ということにします
Dockerfile# Build Frontend StaticfilesFROM node:14.15.1-stretch as front-buildWORKDIR /frontendCOPY ./frontend /frontendRUN yarn installRUN yarn build # frontend/dist にビルドされる# Build Backend Django PackagesFROM python:3.8.6-busterWORKDIR /backendCOPY ./backend /backendCOPY --from=front-build /frontend/dist /frontend/distRUN pip install --no-cache-dir -q -r requirements.txtCMD gunicorn config.wsgi:application --bind 0.0.0.0:$PORT # $PORTは実行時の環境変数から取得される
こんな感じでマルチステージビルドを使って、イメージが構築されます
CMD と ENTRYPOINT について
$ heroku run
のときの環境で Dockerfile の各値が使われます
WORKDIR
: カレントディレクトリENV
: 環境変数ENTRYPOINT
:$ heroku run <cmd>
→$ <ENTRYPOINT> <cmd>
てな感じ。
Docker-Compose のくせで、
Dockerfile# ...ENTRYPOINT [ "./entrypoint.sh" ]CMD [ "gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:$PORT" ]
こんな感じで entrypoint.sh
を ENTRYPOINT
に指定して、CMD
を渡したんですけど、毎回 entrypoint.sh
が呼ばれてしまって $ heroku run
がまともに使えませんでした
なので、ENTRYPOINT
は指定せずに
Dockerfile# ...CMD [ "./entrypoint.sh" ]
とする必要があります
起動前に走らせたいものがないなら、直接サーバーを起動しても良いですけど、配列型で指定すると変数が展開されないので注意が必要です
DockerfileCMD [ "gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:$PORT" ] # NGCMD gunicorn config.wsgi:application --bind 0.0.0.0:$PORT # OK
デプロイする
Heroku CLI
のセットアップとログイン、アプリ作成(heroku create
)がされていることは前提として、
bash$ heroku container:push web
で、ローカルの Dockerfile
を参照した Docker イメージのビルドが行われます
今回のやり方では、デプロイがリポジトリと連動しません
bash$ heroku container:release web
によって、ビルドされたイメージがリモートに送信され、デプロイされます
一連の流れとして、
deploy.shheroku container:push webif [ $? = "0" ]; thenheroku container:release webheroku logs -tfi
辺りを定義しておくと良さそうでした
終わりに
以上です
実際にデプロイするには、
- Nginx 等を挟まないので、collectstatic して、whitenoise で配信する
- SPA のルーティング設定
- Heroku Postgres に環境変数から接続情報を取得して繋ぐ
とかとか、もろもろすることはありますが、その辺は主題じゃないので割愛しました
ステージング環境用に作ったんですが、結構良さそうだったので良ければぜひ。