HerokuにDjangoプロジェクトのDockerイメージをビルドしてデプロイする方法

今回は仕事でHerokuの環境にDockerイメージをビルドしてデプロイすることがあったので、その時に躓いたことも含めて、その方法をまとめておきます。

Dockerとは

まずDockerについてです。この辺の知識も少し曖昧だったのでまとめておきます。Dockerとは簡単に言うと仮想環境をコンピュータ上に作成することができ、またそれらを共有することができるプラットフォームです。

Dockerは、コンテナ仮想化を用いてアプリケーションを開発・配置・実行するためのオープンプラットフォームである。 Dockerはコンテナ仮想化を用いたOSレベルの仮想化によりアプリケーションを開発・実行環境から隔離し、アプリケーションの素早い提供を可能にする。

Wikipediaから引用:Docker - Wikipedia


細かい知識は今回は省略して、よく使うことのみを書いていきます。

①Dockerfileの作成

DockerはDockerfileをいうものを使ってイメージを作成することができます。基本的に公開されている公式のdockerイメージを元に自分のイメージを作ります。イメージを作成するには以下のコマンドを実行します。(ただし今回は後でdocekr-composeを使ってビルドします。)

docker build  -t  イメージ名:タグ名 

Dockerfileは今回は以下のようになります。

# Python3のイメージを基にする
FROM python:3
ENV PYTHONUNBUFFERED 1
# ビルド時に/codeというディレクトリを作成する
RUN mkdir /code
# ワークディレクトリの設定
WORKDIR /code
# requirements.txtを/code/にコピーする
ADD requirements.txt /code/
# requirements.txtを基にpip installする
RUN pip install -r requirements.txt
ADD . /code/

#nodejsをインストールする
RUN curl -SL https://deb.nodesource.com/setup_15.x | bash
RUN apt-get install -y nodejs

ちなみにFROMを見ると、python3のイメージを基にしています。Docker公式でもDjangoのプロジェクトだとこのような書き方をしています。クィックスタート: Compose と Django — Docker-docs-ja 19.03 ドキュメント このPythonイメージとは、Linux環境にPythonがあらかじめインストールされているイメージです(デフォルトはDebian)。つまり環境はLinuxであるということに変わりないので例えば、debianのイメージを基にして、その後Pythonをインストールしても同じです。

These are the suite code names for releases of Debian and indicate which release the image is based on.

https://hub.docker.com/_/python

②Docker-composeについて

Docker-composeとは、複数のコンテナをまとめて扱えるようにするツールです。Dockerをインストールした後でDocker-composeをインストールします。docker-compose.ymlファイルを作成して、次のコマンドでイメージをビルドし,
その次のコマンドでコンテナを起動します。

docker-compose build
(ログあり docker-compose build --progress=plain)
docker-compose up -d
(ログあり docker-compose up) 

Djangoでの一般的なdocker-compose.ymlファイルは以下のようになります。

services:
  # データベース
  db:
    image: postgres
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
  # Djangoアプリ
  web:
    build: .
    command: python3 manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db
volumes:
  postgres_data:

Herokuとは

Herokuとは簡単に言うとアプリケーションを公開することができるクラウドサービスです。Herokuの環境ではアプリケーションはDynoというLinuxコンテナで実行されます。このあたりは知らなかったので面白かったです。

Heroku ではアプリをデプロイするだけでそのコードと依存関係ファイルがパッケージとしてコンテナに格納されるので、ハードウェアや仮想マシンの管理が不要になるというメリットがあります。コンテナとは、コンピューティングリソース、メモリ、OS、一時的なファイルシステムを備えた軽量の独立した環境です。
(中略)
Heroku で使用されるコンテナは「dyno」と呼ばれます。Dyno はユーザーが指定したコマンドにもとづいてコードを実行するように設計された Linux コンテナであり、それぞれが相互に隔離された状態で仮想化されています。

Heroku公式から引用:Heroku Dynos | Heroku

上記のようになっているため、例えば、あるdynoのコンテナに入ってコマンドでnpm installを実行してもそのアプリケーション全体にパッケージがインストールされるわけではありません。この辺を勘違いしていてしばらく躓きました。あくまでもそのdynoのファイルシステム内でパッケージがインストールされ、コンテナを抜けると変更も消滅します。

ある dyno で行ったファイルシステムへの変更は、ほかの dyno には反映されません。
(中略)
dyno のファイルシステムの一時的という特性は、上記のコマンドで確認できます。 heroku run bash​ (dyno 上の Unix シェル) を実行して One-off dyno を作成し、その dyno でファイルを作成してから、セッションを終了すると、その変更は消滅します。 同じアプリケーション内の dyno であっても、すべての dyno は分離されており、セッションが終了すると、dyno は強制終了されます。新しい dyno は、ほかの dyno の状態を継承するのではなく、常に slug から作成されます。

Heroku公式から引用:Heroku の仕組み | Heroku Dev Center

HerokuにDockerイメージをビルドしてデプロイする方法

それでは本題に入っていきます。Heroku公式ページ(heroku.yml を使用して Docker イメージをビルドする | Heroku Dev Center)と同じようにデプロイしていきます。
重要なことは以下の3つです。
①アプリのスタックをcontainerに設定する。
②heroku.ymlを作成して、必要なことを記述します。
③デプロイする。

まず①についてです。HerokuのスタックとはOSのイメージみたいです(参考:スタック | Heroku Dev Center)。デフォルトでは最新のHeroku-20というubuntuを基にしたものになっています。これを以下のコマンドでcontainerに変更します

heroku stack:set container

②については実際に今回書いたheroku.ymlを以下に載せます。このファイルにおけるrunは同じ階層にProcfileがあっても優先されます。

build:
  docker:
    web: Dockerfile
  config:
    DISABLE_COLLECTSTATIC: 1
release:
  command:
    - python manage.py migrate
  image: web
run:
  web: >
      bash -c "npm install  &&
      gunicorn プロジェクト名.wsgi"

releaseはビルドする時に実行されます。imageに関連するコンテナを書く必要があります。runはアプリ起動時に実行されるコマンドです。今回はnpmでパッケージをインストールし、Djangoのサーバーを起動します。

注意点

以上のようにすれば、HerokuにDockerイメージをデプロイできますが、いくつか注意する点があるので書いておきます。

  • npmでパッケージをインストールする際にディレクトリを指定する時は以下のようにします。
npm install --prefix ./ディレクトリ名 install ./ディレクトリ名
  • Djangoではデプロイした環境でcsrfのエラーがでました。以下の設定をsetting.pyに記述します。
CSRF_TRUSTED_ORIGINS = [
    URL(例 'https://hemant-to-do.herokuapp.com')
]

参考:python - CSRF verification failed. Request aborted. only for login page - Stack Overflow

参考サイト一覧

node.js - Build with Heroku.yml fails with unknown error - Stack Overflow
https://stackoverflow.com/questions/63256574/build-with-heroku-yml-fails-with-unknown-error docker-compose & node.js 開発環境構築
https://zenn.dev/takeo/articles/8c06f2420c328c サブフォルダにある package.json で npm install したいときの prefix オプションを実験してうまくいったメモ – 1ft-seabass.jp.MEMO
https://www.1ft-seabass.jp/memo/2021/09/29/sub-folder-npm-prefix-and-install/ npm5から導入された package-lock.jsonについて - kakts-log
https://kakts-tec.hatenablog.com/entry/2017/06/05/020037 DockerでReact + Django + Postgresの連携・SPA構築チュートリアル | Hodalog
https://hodalog.com/tutorial-django-rest-framework-and-react/ DockerでDjango・React環境を構築する - Qiita
https://qiita.com/shiranon/items/b3efd3ed7ce473c6ad83 【Docker】Dockerfileで記述するRUNとCMDとENTRYPOINTの違いについて - 自由気ままに書いちゃおう
https://www.guri2o1667.work/entry/2021/06/08/%E3%80%90Docker%E3%80%91Dockerfile%E3%81%A7%E8%A8%98%E8%BF%B0%E3%81%99%E3%82%8BRUN%E3%81%A8CMD%E3%81%A8ENTRYPOINT%E3%81%AE%E9%81%95%E3%81%84%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6 Python, pipでrequirements.txtを使ってパッケージ一括インストール | note.nkmk.me
https://note.nkmk.me/python-pip-install-requirements/ DockerとCircleCIを使ってRailsアプリの自動テスト&自動Herokuへのデプロイ環境を作った - しふみんのブログ
https://shifumin.hatenablog.com/entry/2018/04/18/223000 Dockerで作成したWebサイトをHerokuでデプロイする方法 - Qiita
https://qiita.com/chisaki0606/items/18ca9e16376ca08adf0f HerokuのProcfileの役割 - あかんわ
https://b0npu.hatenablog.com/entry/2016/12/28/210840 Heroku での Node.js アプリのデプロイ | Heroku Dev Center
https://devcenter.heroku.com/ja/articles/deploying-nodejs Heroku の Node.js サポート | Heroku Dev Center
https://devcenter.heroku.com/ja/articles/nodejs-support#build-behavior heroku.yml解説編。Docker環境のRails6をHerokuにデプロイする(1/2) - 独学プログラマ
https://blog.cloud-acct.com/posts/u-docker-rails-herokuyml/ Rails5+MySQL+DockerをHerokuにデプロイする方法 | ゆみしん夫婦のブログ
https://yumishin.com/rails5-mysql-docker-heroku/ HerokuでDockerイメージをビルドしてデプロイ | (株) クオリティスタート
https://quality-start.in/2021/08/heroku%E3%81%A7docker%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92%E3%83%93%E3%83%AB%E3%83%89%E3%81%97%E3%81%A6%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4/ heroku.yml を使用して Docker イメージをビルドする | Heroku Dev Center
https://devcenter.heroku.com/ja/articles/build-docker-images-heroku-yml リリースフェーズ | Heroku Dev Center
https://devcenter.heroku.com/ja/articles/release-phase heroku.yml を使用して Docker イメージをビルドする | Heroku Dev Center
https://devcenter.heroku.com/ja/articles/build-docker-images-heroku-yml heroku.ymlでRailsをデプロイする | skyplace
https://soranoba.net/programming/rails-on-heroku-with-docker#herokuyml%E3%81%A7%E8%B5%B7%E5%8B%95%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B python - Django app runs locally but I get CSRF verification failed on Heroku - Stack Overflow
https://stackoverflow.com/questions/70675453/django-app-runs-locally-but-i-get-csrf-verification-failed-on-heroku python - CSRF verification failed. Request aborted. only for login page - Stack Overflow
https://stackoverflow.com/questions/70890682/csrf-verification-failed-request-aborted-only-for-login-page django - CSRF verification failed. Request aborted.No solution works for me on the internet - Stack Overflow
https://stackoverflow.com/questions/70416789/csrf-verification-failed-request-aborted-no-solution-works-for-me-on-the-intern django - How to config the heroku.yml to use the Heroku PostgreSQL addon? - Stack Overflow
https://stackoverflow.com/questions/55827138/how-to-config-the-heroku-yml-to-use-the-heroku-postgresql-addon Django+MySQL+Dockerの環境をHerokuにデプロイする - Qiita
https://qiita.com/akki-memo/items/278bb1928280619f8463 Heroku の仕組み | Heroku Dev Center
https://devcenter.heroku.com/ja/articles/how-heroku-works Heroku Dynos | Heroku
https://jp.heroku.com/dynos