DjangoアプリをPythonAnywhereにデプロイする方法

## はじめに

作成したWebアプリをそのままGitHubに上げて、ソースコードをダウンロードしてもらい、実行してもらうことで、アプリを使ってもらうことも可能ですが、その場合ソースコードをダウンロードする必要があったり、実行に必要な依存ライブラリもダウンロードしてもらう必要があるなど、手間がかかります。そこで、作成したアプリを簡単に利用してもらうために外部サーバにデプロイするという方法があります。

PythonAnywhereへのデプロイは [Django GIrlsのチュートリアル](https://tutorial.djangogirls.org/ja/deploy/) を見ればできます。Django Girlsは、初心者の女性を対象に、PythonやDjangoのワークショップを開催する非営利団体で、「Django Girlsのチュートリアル」は、そこで使われている資料になります。初心者を対象にしているため比較的分かりやすい内容となっています。

しかし、セキュリティ対策の説明がよく分からなかったり、個人的に何をしているか分からない手順はやりたくないため、本記事ではより丁寧に説明することを目指しました。とはいってもチュートリアルで説明されていることは本記事で書いていなかったりするため、先に [チュートリアル](https://tutorial.djangogirls.org/ja/deploy/) を読んでほしいです。また、基本的なGitの操作やGitHubのことについては知っているものとして、説明していきます。



## PythonAnywhereとは

PythonAnywhereはPythonのWebホスティングサービスです。無料でDjangoアプリをデプロイできるサービスは他にHerokuやAWS等があります。今回はその中でも一番簡単にデプロイできると思われるPythonAnywhereを選びました。

PythonAnywhereの無料枠の特徴について簡単にまとめました。

- いつアクセスしてもプログラムからレスポンスをもらうことができる
- ユーザ名がそのままドメイン名になる
- ディスク容量は512MBまで
- 1つのアカウントにつき1つのアプリしか公開できない
- アプリを起動させ続けるには 3ヶ月に一回はログインして黄色のボタンを押す必要がある
- 以上の条件で良ければ永久無料で使用可能



## セキュリティ対策

早速デプロイ作業をしていきたいところですが、開発環境で使っているソースコードをそのままアップロードするのはセキュリティ上よくないため、セキュリティ対策をしていきます。まず開発環境と本番環境で設定ファイルを変えるために、configフォルダに新たにlocal_settings.pyという名前で空のファイルを作ってください。configフォルダはsettings.pyを入れているフォルダです。本番環境では「settings.py」の設定を使い、開発環境では「settings.pyの設定の一部をlocal_settings.pyで上書きしたもの」を使います。



### DEBUGの設定

まずsettings.pyのDEBUGをFalseに変更してください。

```python
DEBUG = False
```

「現場で覚えるDjangoの教科書」では、DEBUGについて以下のように説明されています。

> 「DEBUG」は、開発モードと本番モードを切り替えられるようにする設定。開発時はTrueにしておくことで、エラー発生時に画面にデバッグ情報が出力されるなど開発に便利な機能が提供されている。本番稼働時にはセキュリティ面を考慮してこの「DEBUG」設定をFalseにしておく必要がある。

ということで開発環境ではDEBUGをTrueにするために、local_settings.pyに以下の文を追加します。

```python
DEBUG = True
```



### ALLOWED_HOSTSの設定

次にALLOWED_HOSTSを編集します。settings.pyのALLOWED_HOSTSを以下のように変更してください。

```python
ALLOWED_HOSTS = ['localhost', '.pythonanywhere.com', '{|ユーザ名|}.pythonanywhere.com']
```

この設定に関してはあまり情報がなく詳しい説明はできません。これより少ないコードでもできるかもしれません。デプロイしてアクセスした後にBad Requestと出たらこのALLOWED_HOSTSを疑った方が良いです。

> 備考：
>
> ```
> {|ユーザ名|}
> ```
>
>
> ではPythonAnywhereのユーザ名を指定する必要があります。まだPythonAnywhereのアカウントは作成していないため、作成するつもりのユーザ名を入れてください（後で作ります）。例えばユーザ名を pa_name という名前で登録するつもりならALLOWED_HOSTSには次のように設定してください。この後、単に{|ユーザ名|}と書いた場合PythonAnywhereのユーザ名を指すことにします。
>
> ```python
> ALLOWED_HOSTS = ['localhost', '.pythonanywhere.com', 'pa_name.pythonanywhere.com']
> ```

一方、local_settings.pyには以下の文を追加します。

```python
ALLOWED_HOSTS = [‘*’]
```



### https関連の設定

settings.pyに以下の文を追加してください。

```python
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
```

[Djangoデプロイチェックリスト](https://docs.djangoproject.com/ja/2.2/howto/deployment/checklist/) ではこの設定について以下のように説明されています。

> CSRF_COOKIE_SECUREをTrueにすることで、誤ってHTTPによりクッキーを送信してしまうのを防ぎます。
> SESSION_COOKIE_SECUREをTrueにすることで、誤ってHTTPによりセッションクッキーを送信してしまうのを防ぎます。

クッキーとは、「ログイン情報」や「ショッピングサイトのカートの中身」等を保存するための小さなデータファイルのことです。この設定がFalseになっていると、クッキー情報が、https（暗号化されたhttp通信）のときだけでなくhttp（暗号化されていないhttp通信）のときにも、送られてしまうと言っているのだと思います。
「クッキー情報はそれを発行したWebサイトでないと解読できない」と書いているサイトもあったので、これをFalseにしたままで本当に危険なのか分かりませんが、Trueにしておくデメリットはほぼないと思うので、Trueに設定しましょう。



### シークレットキーを隠す

シークレットキー（ソースコードの下に説明を書きました）はデフォルトでDjangoのソースコードに組み込まれていて、実際に運用する際にシークレットキーがソースコードに書かれているとばれやすいため、ソースコードにはシークレットキーの「乱数を用いた生成方法」だけ書いて、実際のシークレットキーは本番環境（サーバ側）で生成するようにします。

シークレットキーは、悪意ある攻撃者に推測されないように十分長くてランダムな（単純でない）文字列であることが求められますが、生成する関数がdjangoに標準で用意されているので、それを呼ぶことにします。以下のコードをsettings.pyに追加してください（挿入する場所はどこでも良いです）。

```python
from django.core.management.utils import get_random_secret_key
SECRET_KEY = get_random_secret_key()  
```

>シークレットキーとは
>
>シークレットキーについて話を始める前にトークンについて説明します。トークンはWebアプリのユーザを識別するために使用する識別子です。つまりWebアプリを使用するAさんのトークンが誰かに知られると、その誰かはAさんのふりをしてWebアプリを使用できる可能性があります。
>
>Djangoのシークレットキーは、Webアプリのユーザのトークンの生成をするときのシード値のようなものになっています。このシークレットキーからトークンを生成する方法はDjangoの仕様を見ればわかるらしいので、シークレットキーが誰かにばれることでトークンもばれやすくなります。トークンがばれると、Webアプリのユーザが被害を受けますが、シークレットキーが誰かにばれるとことで、その誰かはWebアプリの開発者のふりもできたりすることがある。そのほかにも、Webアプリのユーザとのやり取りを暗号化したりするときの秘密鍵にもなっていたり、様々な用途があるらしいです。



###   .gitignoreの設定

無駄なファイルをPythonAnywhereにアップロードしないようにするために、.gitignoreに「本番環境に上げないファイル」を指定して、プロジェクトディレクトリ直下に保存してください。今回は以下のようにファイルを指定しました。

```.gitignore
.spyproject
db.sqlite3
local_settings.py
```

- 「.spyproject」は筆者が使っているエディタであるSpyderの設定ファイルで、これを本番環境に上げる必要はないため指定しています。
- 「db.sqlite3」は開発環境で使うデータベースのファイルで、本番環境のデータベースとは別にするために指定しています。
- 「local_settings.py」は特に重要で、local_settings.pyは開発環境での設定ファイルなので、本番環境には上げないようにしてください。



### local_settings.pyを読み込む

local_settings.py に DEBUG と ALLOWED_HOSTS の設定を書きましたが、local_settings.pyは今のところどのプログラムからも読み込まれていないため、settings.pyから読み込むようにします。その時に、本番環境ではlocal_settings.pyがないため、local_settings.pyがなくてもエラーでプログラムが落ちないようにtry文を使います。

以下のプログラムをsettings.pyの一番最後に追加してください。

```python
try:
    from .local_settings import *
except:
    pass
```





## PythonAnywhereへのデプロイ方法

### Gitのインストール

まず、PythonAnywhereにソースコードをアップする必要があります。PythonAnywhereにソースコードを直接アップすることもできるみたいですが、セキュリティ上よくないためGitHubにいったん上げてからPythonAnywhereにクローンする方法をとります。

Git 自分の PC に入っていない場合は [公式サイト](https://gitforwindows.org) からダウンロードし、インストールしてください。Anacondaの仮想環境を使っている人は、仮想環境からも Git を使えるようにするためにパスを通す必要があります。[この記事](https://qiita.com/masa_ramen/items/0111ed3508d7f2e8e9a6) 等を参考にパスを通してください。



### GitHubにソースコードをアップする

[GitHubの公式サイト](https://github.com) からリポジトリを作成し、そこにソースコードをアップしてください。リポジトリを作るときはできればプライベートリポジトリにした方が良いです。以下のコマンドをプロジェクトディレクトリで実行してください（manage.py や .gitignore と同じ階層に .git が作られることになります）。

```
$ git init
$ git config user.name {|GitHubのユーザ名|}
$ git config user.email {|メールアドレス|}
$ git add .
$ git commit -m “first commit”
$ git remote add origin https://github.com/{|GitHubのユーザ名|}/{|リポジトリ名|}.git
$ git push origin master
```

このへんのコマンドは新しいリポジトリを作った後に表示されるサイトに載っているのをコピペすれば良いです（詳しい説明はネットにたくさん落ちてるので省きます）。そのサイトではブランチmainを作成していますが、しなくても大丈夫です。また git push -u origin main のように -u オプションをつけることで次回から git push とするだけで自動的に origin main にコードを上げてくれるようになります。今回は -u オプションなしで実行しました（個人の好みの問題）。

> 備考1：
> コマンドの最初に書かれているドルマーク（= $）は、そのあとの文がコマンドであることを示すものなので、実際にコマンドプロンプトなどに打ち込むときは、その右にある文を打ち込んでください。

> 備考2：
>
> ```
> {|GitHubのユーザ名|}
> ```
>
>
> ではGitHubのユーザ名を指定する必要があります。例えばGitHubのユーザ名が git_name なら2個目のコマンドは以下のように打ち込んでください。
>
> ```
> $ git config user.name git_name
> ```

> 備考3：
>
> ```
> {|メールアドレス|}
> ```
>
>
> では{|GitHubのユーザ名|}で指定したGitHubアカウントのメールアドレスを指定する必要があります。例えばメールアドレスがが email_name@gmail.com なら3個目のコマンドは以下のように打ち込んでください。
>
> ```
> $ git config user.email email_name@gmail.com
> ```



### PythonAnywhereにユーザ登録をする

次にPythonAnywhereの [公式サイト](https://www.pythonanywhere.com/) にアクセスしてユーザ登録をしてください。このときユーザ名がそのままドメイン名になります。ここでユーザ名は「ALLOWED_HOSTSの設定」で設定したユーザ名を入れてください。

登録が終わるとダッシュボードのチュートリアルが始まります。ダッシュボードはPythonAnywhereのシステムをGUIで操作できるようにしたものというイメージで大丈夫だと思います。以下にダッシュボード画面の簡単な説明を書きました。

- 「コンソール」はwindowsでいうコマンドプロンプト、Macでいうターミナルのような感じでコマンド操作をすることができます。厳密にはBashというものです。ただ、ここではコンソールとBashを同じ意味で使うことにします。
- 「Files」ではファイルをGUIで管理することができます。もちろんファイル管理はコンソールから行うこともできますが、グラフィカルで直感的に操作することができます。
- 「Web apps」ではPythonAnywhere上でWebサイトを作ることができます。
- 「Notebooks」は有料サービスで、Jupyter Notebookを使える機能です。



### APIトークンを作成する

以下の手順でAPIトークンを作成してください。

1. ダッシュボード画面右上の「Account」と書いているボタンをクリック
2. 「API Token」と書かれているタブを選ぶ
3. 「Create a new API tokne」のボタンを押す

 

と言われても「APIトークンってなんだよ」って思われた方は以下の説明を読んでください。「そんなことはどうでもいい、デプロイできればそれでいい」って人は読み飛ばしてください。



> APIトークンについて（あくまで推測なので正しい情報とは限りません）
>
> API については、基本的に [このサイト](https://www.contents.digitallab.jp/api.html) を見てもらうと理解できると思います。
>
> ただ、APIというけどPythonAnywhereでWebアプリを作るときの「APIを提供する側」と「APIを利用する側」は何なのかというと、それぞれ「PythonAnywhere」と「自分が作ったWebアプリ」にあたる。つまり自分で作ったWebアプリを公開するためにPythonAnywhereのAPIを使っているということになります。
>
> じゃあPythonAnywhereはどういう仕様を公開しているのかというと「ソースコードをサーバ上でデプロイするコマンド」等です（この解釈があってるかは分かりません）。
>
> 
>
> PythonAnywhereはWebアプリからの要求にこたえるためにそれぞれのWebアプリに設定されたAPIキーが必要です。このAPIキーはもちろん「APIを提供する側」であるPythonAnywhereから発行されます。
>
> つまりPythonAnywhereが、ある「WebアプリA」にAPIキー「abc123」というのを発行したとします（実際はもっと複雑なキーが発行されます）。すると「WebアプリA」はAPIアクセスをするときにこのAPIキーも同時に渡すことで、「WebアプリA」からのアクセスであり「WebアプリB」や「WebアプリC」からのアクセスではないことをPythonAnywhereに伝えることができます。
>
> 別の言い方をすると、PythonAnywhere は Webアプリ に、APIアクセスするときにはこのAPIキーを使いなさいと指示します。するとPythonAnywhereは、APIアクセスがあったときにそれがどのWebアプリからのアクセスか、はたまたWebアプリ以外の悪意のある攻撃者からのアクセスなのか等を判断することができます。
>
> 
>
> でもPythonAnywhereで生成されたAPIキーは、ソースコードにも埋め込んでないしそれ以外にも使ってないため、裏で自動的にAPIキーをPythonAnywhereに送信してるのだと思います。だから始めにAPIキーを生成することで、WebアプリをPythonAnywhereに登録・認識させているのだと思う。
>
> ちなみに、APIトークンを忘れてしまったときはPythonAnywhereのコンソールから echo $API_TOKEN と打つことで確認できます。



### httpsに対応させる

httpでは通信情報が平文で送られます。それを暗号化するために、httpで接続してきたとしてもhttpsにリダイレクトさせるように設定しておいた方がいいです。PythonAnywhereでは以下のようにボタンを押すだけでできます。

1. ダッシュボードの右上にある「Web」ボタンをクリック
2. ページ下の方の「Force HTTPS」の横のDisableと書かれているところを押してEnableにする

設定を反映するには、ページ上側にある「Reload {|ユーザ名|}.pythonanywhere.com」と書かれた緑のボタンを押す必要があります。



### PythonAnywhereのパッケージインストール

以下のコマンドはPythonAnywhereのBashに打ち込み、PythonAnywhereのパッケージをインストールしてください。

```
$ pip3 install --user pythonanywhere
```

> 備考1：
> 普段Windows等ではPython3系かPython2系のどちらかしか入れないが、PythonAnywhereではPython2系とPython3系が共存しているためコマンドを打つとき2系なのか3系なのかきちんと明示してあげる必要があります。

> 備考2
>
> PythonAnywhereのコンソールを開いたときカレントディレクトリがユーザディレクトリになっています。ここにpipを使ってインストールするには管理者権限が必要です。しかし user install といってコマンドに --user オプションを付けることで、管理者権限が必要なディレクトリにもインストールすることができます。

> 備考3：
> 上記コマンドを打つと以下のようなエラーが出ることがあるかもしれませんが、無視して大丈夫だと思います。
>
> ```
> ERROR: py2neo 4.3.0 has requirement click==7.0, but you'll have click 7.1.2 which is incompatible.
> ```
>
> （このエラーを解決しようとするとまた別のエラーが出てきて、その二つのエラーを同時に解決することができなかったため）



### ヘルパーの実行

GitHub からアプリを自動的に構成するためのヘルパーを実行してください。

```
$ pa_autoconfigure_django.py --python=3.8 https://github.com/{|GitHubのユーザ名|}/{|リポジトリ名|}.git
```

> 備考：
> Pythonのバージョンは自分の開発環境で
>
> ```
> $ python –version
> ```
>
>
> と打って確認してください。筆者の環境では Python 3.8.8 と出てきましたが、最後の数字（8）は多分いらないと思います。

するとGitHubのユーザ名とパスワードを求められるので入力してください。入力が終わると、いろいろログが表示されてインストールが完了します。



### 追加パッケージのインストール

ヘルパーを実行したときのログに「numpyがインストールされていません」というようなエラーが出ていたような気がしたので、pipコマンドでnumpyをインストールするコマンドを打ち込みました。

```
$ pip3 install numpy
```

すると以下のようなログが出てきました。

```
Requirement already satisfied: numpy in /usr/lib/python3.8/site-packages  (1.17.3)  
```

既に入ってる？？

種明かしをすると、追加パッケージのインストールは仮想環境のコンソールから打ち込む必要があるみたいです。仮想環境のコンソールは「Web」ボタンから「Start a console in this virtualenv」というのを押すことで開けます。この仮想環境のコンソールで pip3 list でパッケージのリストを確認すると numpy が入っていなかったため pip3 install numpy でインストールしました。

今回のように依存パッケージが numpy だけとかならこの方法でも対処できますが、多くなってくるといちいち pip3 install するのが面倒になってきます。そんなときは requirements.txt というものを作ると良いと思います。これに関しては [このサイト](https://code-schools.com/django-requirements/) 等を参考にしてください。



### 動作確認

ここまでの手順を行うと、以下のURLにサイトが作成されています。

```
https://{|ユーザ名|}.pythonanywhere.com
```

このURLはダッシュボード右上の「Web」ボタンを押すと遷移するページの一番上にある「Configuration for」という文字のところにも書かれています。





## 再デプロイする方法

再デプロイするには、まずデプロイのときと同じようにGitHubにソースコードを上げ直す必要があります。例えば以下のように打ちこんでください。

```
$ git add -a
$ git commit -m “second commit”
$ git push origin master
```



新しくGitHubに上げたソースコードをサーバにインストールし直すのは、最初サーバにダウンロードしたときのコマンドに --nukeオプション をつけ足せば良いみたいです。

```
$ pa_autoconfigure_django.py --nuke --python=3.8 https://github.com/{|GitHubのユーザ名|}/{|リポジトリ名|}.git
```

入力し終わったらもう一度ユーザ名とパスワードの入力を求められるので入力してください。PythonAnywhere側で再インストールのためにすることはこれだけなので、Bashの様子を見て作業が終わってそうなら、きちんとデプロイできたかどうか確認してください。

リロードしないとインストールした結果が反映されないことがまれにあるみたいです。リロードはダッシュボード右上にある「Web」ボタンを押してからページ上らへんに表示される緑のボタンを押すことでできます。