Heroku に SPA on Django の Docker イメージをデプロイする
- # Heroku
- # Docker
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
) - …
みたいなディレクトリ構成ということにします
# Build Frontend Staticfiles
FROM node:14.15.1-stretch as front-build
WORKDIR /frontend
COPY ./frontend /frontend
RUN yarn install
RUN yarn build # frontend/dist にビルドされる
# Build Backend Django Packages
FROM python:3.8.6-buster
WORKDIR /backend
COPY ./backend /backend
COPY /frontend/dist /frontend/dist
RUN pip install --no-cache-dir -q -r requirements.txt
CMD 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 のくせで、
# ...
ENTRYPOINT [ "./entrypoint.sh" ]
CMD [ "gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:$PORT" ]
こんな感じで entrypoint.sh
を ENTRYPOINT
に指定して、CMD
を渡したんですけど、毎回 entrypoint.sh
が呼ばれてしまって $ heroku run
がまともに使えませんでした
なので、ENTRYPOINT
は指定せずに
# ...
CMD [ "./entrypoint.sh" ]
とする必要があります
起動前に走らせたいものがないなら、直接サーバーを起動しても良いですけど、配列型で指定すると変数が展開されないので注意が必要です
CMD [ "gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:$PORT" ] # NG
CMD gunicorn config.wsgi:application --bind 0.0.0.0:$PORT # OK
デプロイする
Heroku CLI
のセットアップとログイン、アプリ作成(heroku create
)がされていることは前提として、
$ heroku container:push web
で、ローカルの Dockerfile
を参照した Docker イメージのビルドが行われます
今回のやり方では、デプロイがリポジトリと連動しません
$ heroku container:release web
によって、ビルドされたイメージがリモートに送信され、デプロイされます
一連の流れとして、
heroku container:push web
if [ $? = "0" ]; then
heroku container:release web
heroku logs -t
fi
辺りを定義しておくと良さそうでした
終わりに
以上です
実際にデプロイするには、
- Nginx 等を挟まないので、collectstatic して、whitenoise で配信する
- SPA のルーティング設定
- Heroku Postgres に環境変数から接続情報を取得して繋ぐ
とかとか、もろもろすることはありますが、その辺は主題じゃないので割愛しました
ステージング環境用に作ったんですが、結構良さそうだったので良ければぜひ。