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

# 概要
DjangoアプリをHerokuにデプロイする方法を書いてみます。

なお、環境は`python 3.7.3`、`django 2.2`

Herokuアカウントを持っている前提になりますので、登録がまだの方は[Heroku公式サイト](https://jp.heroku.com/)から登録してください。

# 1. 手順（インストール編)

###### 1-1. Heroku CLIインストール

```terminal:terminal
$ brew tap heroku/brew && brew install heroku
```

参照：[The Heroku CLI | Download and install](https://devcenter.heroku.com/articles/heroku-cli#download-and-install)

###### 1-2. gunicornインストール

```terminal:terminal
$ pip install gunicorn
```

参照：[Configuring Django Apps for Heroku | The basics](https://devcenter.heroku.com/articles/django-app-configuration#the-basics)

###### 1-3. django-herokuインストール

```terminal:terminal
$ pip install django-heroku
```
参照：[Configuring Django Apps for Heroku | settings.py changes](https://devcenter.heroku.com/articles/django-app-configuration#settings-py-changes)

###### 1-4. dj-database-urlインストール

```terminal:terminal
$ pip install dj-database-url
```

参照：
[Heroku Postgres | Connecting with Django](https://devcenter.heroku.com/articles/heroku-postgresql#connecting-with-django)
[Concurrency and Database Connections in Django | Persistent Connections](https://devcenter.heroku.com/articles/python-concurrency-and-database-connections#persistent-connections)

# 2. 手順（ファイル作成編）

###### 2-1. Procfile作成

下記のコマンドで`Procfile`を作成してください。

プロジェクトフォルダー名は`settings.py`や`wsgi.py`が入っているフォルダー名になります。

また、こちらには拡張子は不要です。（例：ファイル名`.py`）

```terminal:terminal
$ echo web: gunicorn プロジェクトフォルダー名.wsgi --log-file - > Procfile
```

参照：[Getting Started on Heroku with Python | Define a Procfile](https://devcenter.heroku.com/articles/getting-started-with-python#define-a-procfile)

###### 2-2. requirements.txt作成

こちらを実行すると、ファイルにたくさんの書き込みが生成されますが、そのままでokです。

```terminal:terminal
$ pip freeze > requirements.txt
```

参照：
[Getting Started on Heroku with Python | Declare app dependencies](https://devcenter.heroku.com/articles/getting-started-with-python#declare-app-dependencies)
[Deploying Python and Django Apps on Heroku | Expected files for Python](https://devcenter.heroku.com/articles/deploying-python#expected-files-for-python)

###### 2-3. runtime.txt作成

こちらにはPythonのバージョンを記載します。

例えば、`python 3.7.3`を使用していれば下記のようになります。

```terminal:terminal
$ echo python-3.7.3 > runtime.txt
```

バージョンがわからない場合は、下記のコマンドで確認してから、上記のコマンドを実行してください。

```terminal:terminal
$ python --version
```

参照：[Specifying a Python Runtime](https://devcenter.heroku.com/articles/python-runtimes)

___※バージョンによってはサポートされていないのもあるので、[Heroku Supported Runtimes](https://devcenter.heroku.com/articles/python-support#supported-runtimes)から必ず確認してください___

###### 2-4. .gitignore作成

Gitで管理されたくない（無視[ignore]してほしい）ファイルを指定するので、

後ほど説明する`local_settings.py`も指定してあげます。

```terminal:terminal
$ echo -e __pycache__\\ndb.sqlite3\\n.DS_Store\\nlocal_settings.py > .gitignore
```

参照：[GitHub | Python.gitignore](https://github.com/github/gitignore/blob/master/Python.gitignore)

# 3. 手順(ファイル編集編)

###### 3-1. Debug設定

本番環境ではDebugは`False`になり、ローカルでは`True`になるように設定します。

```python:settings.py
DEBUG = False #Trueから変更

###
省略
###

#追加
try:
    from .local_settings import *
except ImportError:
    pass
```

次に`local_settings.py`を`settings.py`と同じディレクトリに作成し、下記のように追加してください。

```python:local_settings.py
import os

#settings.pyからそのままコピー
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

#settings.pyからそのままコピー
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

DEBUG = True #ローカルでDebugできるようになります
```

これを設定したことによって、本番環境では下記のようになり、

![django_heroku_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403981/011f345d-2947-c62e-aa1b-8c072fe64964.png)

ローカル環境ではこのようになります。

![django_heroku_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403981/3f89ccb6-65ff-c91e-20e6-111a0b983079.png)



###### 3-2. SECRET_KEY設定

`SECRET_KEY`を`settings.py`から削除しますが、___次のステップで使うので一旦コピーしてください。___

```python:settings.py
SECRET_KEY = 'ここに記載されているSECRET_KEYをコピーしたら' #行ごと削除
```

参照：
[Django公式ドキュメント | SECRET_KEY](https://docs.djangoproject.com/ja/2.2/ref/settings/#secret-key)
[Django公式ドキュメント | デプロイチェックリスト SECRET_KEY](https://docs.djangoproject.com/ja/2.2/howto/deployment/checklist/#secret-key)

さきほど作成した`local_settings.py`に`SECRET_KEY`を追加します。

```python:local_settings.py
import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

SECRET_KEY = 'さきほどコピーしたSECRET_KEYをここに' #追加

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

DEBUG = True
```

続いて`settings.py`にも`SECRET_KEY`の設定を追加します。

```python:settings.py

STATIC_URL = '/static/'

###
省略
###

#追加
if not DEBUG:
    SECRET_KEY = os.environ['SECRET_KEY']
```

###### 3-3. ALLOWED_HOSTS設定

Herokuで使えるよう`ALLOWED_HOSTS`を編集します。

```python:settings.py
ALLOWED_HOSTS = ['127.0.0.1', '.herokuapp.com']
ALLOWED_HOSTS = ['*'] #こちらでもokです
```

参照：[Django公式ドキュメント | ALLOWED_HOSTS](https://docs.djangoproject.com/ja/2.2/ref/settings/#allowed-hosts)

###### 3-4. whitenoise設定

CSSを反映させるために、下記のように追加します。

```python:settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware', #追加
]
```

参照：
[Django and Static Assets | whitenoise](https://devcenter.heroku.com/articles/django-assets#whitenoise)
[Using WhiteNoise with Django](http://whitenoise.evans.io/en/stable/django.html)

###### 3-5. データベース設定

HerokuだとあらかじめDjangoで設定されている`sqlite3`が動作しないので、`postgresql`に変更し、冒頭でインストールした`dj_database_url`の設定もします。

```python:settings.py
import dj_database_url

###
省略
###

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'name',
        'USER': 'user',
        'PASSWORD': '',
        'HOST': 'host',
        'PORT': '',
    }
}

###
省略
###

db_from_env = dj_database_url.config(conn_max_age=600, ssl_require=True)
DATABASES['default'].update(db_from_env)
```

参照：[SQLite on Heroku](https://devcenter.heroku.com/articles/sqlite3)

###### 3-6. CSSファイル設定

`whitenoise`と同様に`settings.py`にもCSSの設定が必要なので、下記のように追加してください。

```python:settings.py
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
```

参照：
[Django and Static Assets](https://devcenter.heroku.com/articles/django-assets)
[Django公式ドキュメント | 静的ファイル (画像、JavaScript、CSS など) を管理する](https://docs.djangoproject.com/ja/2.2/howto/static-files/#deployment)

###### 3-7. django_heroku設定

冒頭でインストールした`django_heroku`を追加します。

```python:settings.py
if not DEBUG:
    SECRET_KEY = os.environ['SECRET_KEY']
    import django_heroku #追加
    django_heroku.settings(locals()) #追加
```

# 4. 手順(アップロード編)の前に

###### 4-1. Git管理

まだアプリをGitで管理していなければGitに追加しましょう。

もしGit管理していれば[アップロード編](https://qiita.com/frosty/items/66f5dff8fc723387108c#5-%E6%89%8B%E9%A0%86%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E7%B7%A8)に進んでください。

###### 4-2. Git

もしアプリ作成から一度もGit管理していなければ、下記のコマンドを実行しましょう。

```terminal:terminal
$ git init
$ git add -A
$ git commit -m "initial commit"
```

上記のコマンド実行後、ログが見れますので確認してみてください。

```terminal:terminal
$ git log

commit ここに英数字のcommit識別番号が記載されています (HEAD -> master, origin/master, heroku/master)
Author: name <email@example.com>
Date:   Mon Jun 18 13:41:38 2019 +0900

    initial commit
```

# 5. 手順(アップロード編)

###### 5-1. Herokuにログインする

```terminal:terminal
$ heroku login
```

###### 5-2. プロジェクトを作成する

```terminal:terminal
$ heroku create プロジェクト名
```

上記のプロジェクト名がHerokuのURLになります。

```terminal:例えばプロジェクト名をmyprojectにした場合
$ heroku create myproject
-> https://myproject.herokuapp.com/ #このURLになる
```

###### 5-3. SECRET_KEYをHerokuに設定

`settings.py`にあった`SECRET_KEY`を記入してください。

```terminal:terminal
$ heroku config:set SECRET_KEY='settings.pyに記載されていたSECRET_KEYを記入'
```

参照：[Configuration and Config Vars](https://devcenter.heroku.com/articles/config-vars)

###### 5-4. Herokuにプッシュする

```terminal:terminal
$ git push heroku master
``` 

###### 5-5. Dynoを起動させる

```terminal:terminal
$ heroku ps:scale web=1
```

参照：
[dyno：Heroku プラットフォームの中核](https://jp.heroku.com/dynos)
[Scaling Your Dyno Formation | Scaling from the CLI](https://devcenter.heroku.com/articles/scaling#scaling-from-the-cli)

###### 5-6. データベース設定

ローカル開発と同様に、下記コマンドを実行しましょう。

```terminal:terminal
$ heroku run python manage.py migrate
$ heroku run python manage.py createsuperuser
```

###### 5-7. アプリを開く

正しく処理ができていれば、下記コマンドでアプリが開くはずです。

```terminal:terminal
$ heroku open
```

# 6. 参照

- [Deploying with Git](https://devcenter.heroku.com/articles/git)
- [Language Support | Python](https://devcenter.heroku.com/categories/python-support)
- [GitHub | django-heroku](https://github.com/heroku/django-heroku)
- [GitHub | heroku-django-template](https://github.com/heroku/heroku-django-template)

もし違う言語で行う場合は、下記を参考にしてみてください。

- [Complete language-specific setup](https://devcenter.heroku.com/articles/preparing-a-codebase-for-heroku-deployment#6-complete-language-specific-setup)

# 7. まとめ

最後に、ここまで行った処理のコードを下記に記載します。

どこに書いていいのか、わからなくなったら、参考にしてみてください。

###### 7-1. ディレクトリの確認

おおまかですが、下記のようになっているかと思います。

```terminal
Your_Directory_Name
├── Procfile
├── db.sqlite3
├── manage.py
├── requirements.txt
├── runtime.txt
├── static
│   └── style.css
├── templates
│   ├── helloworld.html
├── your_app
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
└── your_project_file_name
    ├── local_settings.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
```

###### 7-2. settings.py

```python:settings.py
import os
import dj_database_url #追加

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

DEBUG = False #Trueから変更

ALLOWED_HOSTS = ['127.0.0.1', '.herokuapp.com'] #追加
ALLOWED_HOSTS = ['*'] #こちらでもok

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'your_app_name', #ご自身のアプリ名
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware', #追加
]

ROOT_URLCONF = 'your_app_name.urls' #ご自身のアプリ名

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'your_app_name.wsgi.application' #ご自身のアプリ名

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2', #sqlite3から変更
        'NAME': 'name',
        'USER': 'user',
        'PASSWORD': '',
        'HOST': 'localhost',
        'PORT': '',
    }
}

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = True

STATIC_URL = '/static/'

STATICFILES_DIRS =(
    os.path.join(BASE_DIR, 'static')
)

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') #追加

#追加
try:
    from .local_settings import *
except ImportError:
    pass

if not DEBUG:
    SECRET_KEY = os.environ['SECRET_KEY']
    import django_heroku
    django_heroku.settings(locals())

db_from_env = dj_database_url.config(conn_max_age=600, ssl_require=True)
DATABASES['default'].update(db_from_env)
```

###### 7-3. local_settings.py

```python:local_settings.py
import os

SECRET_KEY = 'settings.pyに記載されていたご自身のSECRET_KEYを' #追加

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

DEBUG = True
```

###### 7-4. Procfile

```:Procfile
web: gunicorn プロジェクトフォルダー名.wsgi --log-file -
```

###### 7-5. requirements.txt

___※内容はご自身の環境によって異なるので注意してください___

```:requirements.txt
dj-database-url==0.5.0
Django==2.2
django-heroku==0.3.1
gunicorn==19.9.0
psycopg2==2.8.2
pytz==2018.7
whitenoise==4.1.2
psycopg2-binary
```

###### 7-6. runtime.txt

___※内容はご自身の環境によって異なるので注意してください___

```:runtime.txt
python-3.7.3
```

###### 7-7. .gitignore

___※その他に無視してほしいファイルは、ご自身で追加してください___

```:.gitignore
__pycache__
db.sqlite3
.DS_Store
local_settings.py
``` 

以上