Django で作ったウェブサイトをエックスサーバーで公開する方法

こんにちは、柏崎でぃすこです。
note 初投稿です。

さて先日、自身のウェブサイトをリニューアルオープンしました。

当サイトは Django で開発し、エックスサーバー上で公開しています。
「お知らせ」機能や UTAU 音声ライブラリのバージョン管理で、データベースを活用しています。

今回は、Django で開発したウェブサイトをエックスサーバーで公開するまでの手順を紹介します。
ネット上でも情報が少なかったので、試行錯誤・悪戦苦闘を繰り返しました。
その成果を今回、備忘録として共有します。
少しでも参考になればと思います。

なかなか大ボリュームですが、ぜひ最後まで読んでいってください!



はじめに

前提

  • コマンドラインツールを使えること。

  • Python + Django でウェブアプリケーションの開発ができること。

  • Git 管理ができること。

  • エックスサーバーでサーバおよびドメインを契約していること。

全体の流れ

  • Python の仮想環境 pipenv 上で Django アプリケーションを作成し、各種設定を行う。

  • ソースコードは GitHub で管理する。

  • エックスサーバーの各種設定を行う。

  • エックスサーバー上に Anaconda をインストールする。

  • GitHub Actions でデプロイする。


Django アプリケーションの作成と各種設定

まずはローカルの開発環境で Django アプリケーションを作成します。

pipenv 上に Django アプリケーションを作成する

Python の仮想環境 pipenv 上で、Django アプリケーションを作成します。

$ pipenv --python 3
$ pipenv install Django
$ pipenv shell
$ django-admin startproject config .

pipenv や Django の詳細については、ここでは省略します。

Django アプリケーションのソースコードは GitHub で管理します。
私の場合は dev ブランチで開発、master ブランチでリリースという管理にしました。

settings ファイルを編集する

今回は、開発環境と本番環境の他に、テスト環境とステージング環境を作成します。
そのため各環境用に settings.py を編集します。

📁 config/
├ 📁 settings/
│ ├ 📄 __init__.py
│ ├ 📄 base.py
│ ├ 📄 config_dev.py
│ ├ 📄 config_prod.py
│ ├ 📄 config_stage.py
│ └ 📄 config_test.py
├ 📄 __init__.py
├ 📄 asgi.py
├ 📄 urls.py
└ 📄 wsgi.py

元の settings.py を settings/ フォルダへ移動し、base.py にリネームします。
このとき、__init__.py (空のファイル) の作成も必要になります。

base.py では、各環境に共通する項目を設定します。
自身のディレクトリが変わったため、BASE_DIR を以下の通りに変更します。

BASE_DIR = Path(__file__).resolve().parent.parent.parent

config_dev.py では、開発環境用の項目を設定します。

  • SECRET_KEY はデフォルト値とします。

  • デバッグを有効にします。

  • ホスト名は localhost を設定します。

  • データベースはデフォルトの SQLite を使用します。

from .base import *

SECRET_KEY = "django-insecure-***"

DEBUG = True

ALLOWED_HOSTS = ["localhost"]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}

config_test.py では、テスト環境用の項目を設定します。
ここでは、エックスサーバー上でデバッグを行うための環境として作成します。

  • SECRET_KEY は、サーバの環境変数から取得します。
    (環境変数の内容は、後の手順で登録します。)

  • デバッグを有効にします。

  • テスト環境はエックスサーバーのサブドメインで動かすので、ホスト名にサブドメイン名を設定します。
    (サブドメインは、後の手順で作成します。)

  • データベースは、エックスサーバーが提供する MySQL を使用します。
    接続先の設定は、サーバの環境変数から取得します。
    (環境変数の内容は、後の手順で登録します。)

  • 静的ファイルを正しく読み込むため、STATIC_ROOT を設定します。

import os

from .base import *

SECRET_KEY = os.environ["SECRET_KEY"]

DEBUG = True

ALLOWED_HOSTS = ["test.***.com"]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "HOST": os.environ["DATABASE_HOST"],
        "PORT": os.environ["DATABASE_PORT"],
        "NAME": os.environ["DATABASE_NAME"],
        "USER": os.environ["DATABASE_USERNAME"],
        "PASSWORD": os.environ["DATABASE_PASSWORD"],
        "TEST": {
            "NAME": os.environ["DATABASE_NAME_UNITTEST"],
        },
        "OPTIONS": {
            "init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
        },
    },
}

STATIC_ROOT = BASE_DIR / "static/"

config_stage.py では、ステージング環境用の項目を設定します。
ここでは、デバッグを無効にして本番環境と同様の動作を確認するための環境として作成します。

import os

from .base import *

SECRET_KEY = os.environ["SECRET_KEY"]

DEBUG = False

ALLOWED_HOSTS = ["stage.***.com"]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "HOST": os.environ["DATABASE_HOST"],
        "PORT": os.environ["DATABASE_PORT"],
        "NAME": os.environ["DATABASE_NAME"],
        "USER": os.environ["DATABASE_USERNAME"],
        "PASSWORD": os.environ["DATABASE_PASSWORD"],
        "OPTIONS": {
            "init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
        },
    },
}

STATIC_ROOT = BASE_DIR / "static/"

SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True

SECURE_SSL_REDIRECT = True

SESSION_COOKIE_SECURE = True

CSRF_COOKIE_SECURE = True

SECURE_HSTS_PRELOAD = True

config_prod.py では、本番環境用の項目を設定します。
内容はステージング環境のものと同様ですね。

import os

from .base import *

SECRET_KEY = os.environ["SECRET_KEY"]

DEBUG = False

ALLOWED_HOSTS = ["***.com"]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "HOST": os.environ["DATABASE_HOST"],
        "PORT": os.environ["DATABASE_PORT"],
        "NAME": os.environ["DATABASE_NAME"],
        "USER": os.environ["DATABASE_USERNAME"],
        "PASSWORD": os.environ["DATABASE_PASSWORD"],
        "OPTIONS": {
            "init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
        },
    },
}

STATIC_ROOT = BASE_DIR / "static/"

SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True

SECURE_SSL_REDIRECT = True

SESSION_COOKIE_SECURE = True

CSRF_COOKIE_SECURE = True

SECURE_HSTS_PRELOAD = True

最後に config/asgi.py, config/wsgi.py, manage.py の 3 ファイルを編集します。
開発環境用の設定ファイルをデフォルトとして設定します。

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.config_dev")

(2023-10-24 追記)
これまで各環境の設定ファイル名は、「dev.py」「test.py」のようにしていましたが、ローカル環境での単体テスト実施時に「test.py」が読み込まれてしまいエラーが発生するという事象が発生してしまったため、「config_dev.py」「config_test.py」のように修正しました。

MySQL クライアント「PyMySQL」をインストールする

Django から MySQL を利用するために、MySQL クライアントのインストールが必要です。
後の手順でエックスサーバー上に pipenv 環境を作成し、そこで MySQL クライアントを使用するので、開発環境の段階でインストールしておきます。

MySQL クライアントについて、Django 公式では「mysqlclient」の利用が推奨されています。
しかし、私が試行錯誤した結果、エックスサーバー上に mysqlclient をインストールできませんでした。
そのため、ここでは「PyMySQL」(Python 純正の MySQL クライアント) を使用します。

$ pipenv install PyMySQL

manage.py を編集します。
install_as_MySQLdb() で PyMySQL を有効化します。

import os
import sys

import pymysql

pymysql.install_as_MySQLdb()

def main():
    # 以下省略……

残りのパッケージのインストール

最後に「python-dotenv」というパッケージのインストールも行います。
.env ファイルから環境変数を読み込むためのパッケージで、後の手順で必要になります。

$ pipenv install python-dotenv

エックスサーバーの各種設定

ここからはエックスサーバー上で各種設定を行います。

サブドメインを作成する

エックスサーバーのサブドメイン機能を利用して、テスト環境とステージング環境を作成します。

サーバーパネル > ドメイン > サブドメイン設定

以下のサブドメインを作成します。

  • test.***.com

  • stage.***.com

ドキュメントルートは、「/***.com/public_html/(サブドメイン名).***.com」を選びました。

続いて、作成したサブドメインにアクセス制限を設定します。

サーバーパネル > ホームページ > アクセス制限

先ほど作成したサブドメインのフォルダを選択し、ユーザ設定を行い、アクセス制限を ON にします。

データベースを作成する

今回はデータベースに、エックスサーバーで提供される MySQL (MariaDB) を使用します。

MySQL データベースと MySQL ユーザを作成します。

サーバーパネル > データベース > MySQL 設定

以下のデータベースを作成します。

  • xs******_test (テスト環境用)

  • xs******_unittest (テスト環境で実行する単体テスト用)

  • xs******_stage (ステージング環境用)

  • xs******_prod (本番環境用)

MySQL ユーザは以下の通りです。

  • xs******_test (テスト環境用)

  • xs******_stage (ステージング環境用)

  • xs******_prod (本番環境用)

これらのユーザに、データベースのアクセス権限をそれぞれ付与します。

※ データベース「xs******_unittest」は、「xs******_test」と同じユーザを使用します。

エックスサーバーに SSH 接続する

まずはエックスサーバーの SSH 接続の設定を行います。

サーバーパネル > アカウント > SSH 設定

公開鍵認証用鍵ペアを生成し、秘密鍵をダウンロードします。
「id_rsa」といったファイル名をつけてください。

以下のコマンドで接続できます。

$ ssh -l xs****** -i (秘密鍵ファイル名) -p 10022 xs******.xsrv.jp

私の場合は、上記コマンドを実行する Windows バッチファイルを作成し、ダブルクリックで SSH 接続できるようにしました。

エックスサーバーに Anaconda をインストールする

エックスサーバーで Python 3 を動かすため、Anaconda をインストールします。
Python 3 を動かす方法は色々ありますが、多分これがいちばん簡単だと思います。

https://www.anaconda.com/download#downloads から Linux 用インストーラ (Anaconda3-(バージョン)-Linux-x86_64.sh) をダウンロードします。

エックスサーバーのファイルマネージャで、ルートディレクトリにインストーラをアップロードします。

SSH 接続したコマンドラインで、インストーラを実行します。

$ sh Anaconda3-(バージョン)-Linux-x86_64.sh

yes / no を聞かれたら全て yes で問題ないと思います。

Anaconda のパッケージを最新化します。

$ conda update --all

pip から pipenv をインストールします。

$ pip install pipenv

これでエックスサーバー上に pipenv 環境を作れるようになりました。

.htaccess を編集する

エックスサーバーのファイルマネージャで .htaccess ファイルを編集します。
.htaccess は、ウェブサイトにアクセスしたときの動作を制御するためのファイルです。

テスト環境

RewriteEngine On

RewriteCond %{REQUEST_URI} !index\.cgi
RewriteCond %{REQUEST_URI} !(^/static/)
RewriteCond %{REQUEST_URI} !(^/media/)
RewriteRule ^(.*)$ /index.cgi/$1 [QSA,L]

AuthUserFile "/home/xs******/***.com/htpasswd/test.***.com/.htpasswd"
AuthName "Member Site"
AuthType BASIC
require valid-user

ステージング環境

RewriteEngine On

RewriteCond %{REQUEST_URI} !index\.cgi
RewriteCond %{REQUEST_URI} !(^/static/)
RewriteCond %{REQUEST_URI} !(^/media/)
RewriteRule ^(.*)$ /index.cgi/$1 [QSA,L]

AuthUserFile "/home/xs******/***.com/htpasswd/stage.***.com/.htpasswd"
AuthName "Member Site"
AuthType BASIC
require valid-user

本番環境

RewriteEngine On

RewriteCond %{REQUEST_URI} !index\.cgi
RewriteCond %{REQUEST_URI} !(^/static/)
RewriteCond %{REQUEST_URI} !(^/media/)
RewriteRule ^(.*)$ /index.cgi/$1 [QSA,L]

AuthUserFile 以下の内容は先ほど設定したアクセス制限で、変更は不要です。
RewriteCond および RewriteRule が今回追記する内容です。

リクエストされた URI の内容を index.cgi に渡し、その内容を Django で表示するという動作です。
(index.cgi は後の手順で作成します。)

RewriteCond (置換条件) で static ディレクトリ, media ディレクトリを除外しています。
そうすることで、静的ファイルおよびメディアファイルに対しては直接アクセスすることができます。

RewriteRule (置換ルール) が実行されると、実行後の内容で再び先頭から RewriteCond が実行されます。
そのため、index.cgi に置換後に無限ループにならないよう index.cgi も除外しています。

【余談】
.htaccess について、以下の内容を紹介しているコンテンツもありました。

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /index.cgi/$1 [QSA,L]

ファイルが存在するかどうかで、直接アクセスするか Django URL にするかを分けています。
しかし、これだとファイル名がわかってしまえば、サーバ内部のソースコードや機密情報が全て入手できてしまいますよね。
ありえません。

不要なファイルを削除する

各ドメインのフォルダにデフォルトで置かれている以下のファイルは不要なので、削除して構いません。

  • default_page.png

  • index.html


GitHub Actions で Django アプリケーションをエックスサーバーにデプロイする

さてここから、お待ちかねのデプロイ手順に入ります。

Secrets の作成

サーバの環境変数に設定したり、デプロイ時に使用したりするための各項目を、GitHub の Secrets に登録します。

GitHub リポジトリ > Settings > Security > Secrets and variables > Actions

以下の Secrets を登録します。(名前昇順)

  • DATABASE_HOST … 「localhost」を設定

  • DATABASE_NAME_PROD … 「データベースを作成する」で作成したデータベース名を設定 (本番環境用)

  • DATABASE_NAME_STAGE … 「データベースを作成する」で作成したデータベース名を設定 (ステージング環境用)

  • DATABASE_NAME_TEST … 「データベースを作成する」で作成したデータベース名を設定 (テスト環境用)

  • DATABASE_NAME_UNITTEST … 「データベースを作成する」で作成したデータベース名を設定 (テスト環境で実行する単体テスト用)

  • DATABASE_PASSWORD_PROD … 「データベースを作成する」で作成した MySQL ユーザのパスワードを設定 (本番環境用)

  • DATABASE_PASSWORD_STAGE … 「データベースを作成する」で作成した MySQL ユーザのパスワードを設定 (ステージング環境用)

  • DATABASE_PASSWORD_TEST … 「データベースを作成する」で作成した MySQL ユーザのパスワードを設定 (テスト環境用)

  • DATABASE_PORT … 「3306」を設定

  • DATABASE_USERNAME_PROD … 「データベースを作成する」で作成した MySQL ユーザのユーザ名を設定 (本番環境用)

  • DATABASE_USERNAME_STAGE … 「データベースを作成する」で作成した MySQL ユーザのユーザ名を設定 (ステージング環境用)

  • DATABASE_USERNAME_TEST … 「データベースを作成する」で作成した MySQL ユーザのユーザ名を設定 (テスト環境用)

  • FTP_HOST … 「sv***.xserver.jp」を設定

  • FTP_PASSWORD … FTP パスワード (サーバパスワードと同一) を設定

  • FTP_USERNAME … 「xs******」を設定

  • SECRET_KEY … 自動生成した Django 秘密鍵を設定
    「django-secure-***」のような値を生成する。
    参考: https://miniwebtool.com/ja/django-secret-key-generator/

  • SSH_HOST … 「xs******.xsrv.jp」を設定

  • SSH_PASSPHRASE … 「エックスサーバーに SSH 接続する」で設定したパスフレーズを設定

  • SSH_PORT … 「10022」を設定

  • SSH_PRIVATE_KEY … 「エックスサーバーに SSH 接続する」でダウンロードした秘密鍵ファイルの内容を設定

  • SSH_USERNAME … 「xs******」を設定

GitHub Actions でまずはテスト環境へデプロイする

GitHub Actions を使用して、エックスサーバーへの自動デプロイを行います。
今回は dev ブランチにプッシュされたときにテスト環境とステージング環境へデプロイ、master ブランチにプッシュされたときに本番環境へデプロイするように設定します。

.github/workflows/ 配下に yml ファイルを作成します。

今回は各ブランチへのプッシュ (またはマージ) をトリガーとして、デプロイを実行するジョブを書きました。

.github/workflows/testing-deploy.yml

# テスト環境へデプロイする
name: Testing Deploy

on:
  # dev ブランチへプッシュされたとき
  push:
    branches:
      - dev

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3.5.3

      # FTP 経由でファイルをサーバへ転送する
      - name: FTP Deploy
        uses: SamKirkland/FTP-Deploy-Action@v4.3.4
        with:
          server: ${{ secrets.FTP_HOST }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          server-dir: ***.com/public_html/test.***.com/
          exclude: |
            **/.**/**
            .editorconfig
            .gitignore

      # SSH 経由でコマンドを実行する
      - name: SSH Remote Commands
        uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.SSH_HOST }}
          port: ${{ secrets.SSH_PORT }}
          username: ${{ secrets.SSH_USERNAME }}

          key: ${{ secrets.SSH_PRIVATE_KEY }}
          passphrase: ${{ secrets.SSH_PASSPHRASE }}
          script: |
            cd ***.com/public_html/test.***.com/

            # .env を作成
            cat << EOF > .env
            DJANGO_SETTINGS_MODULE=config.settings.config_test
            SECRET_KEY=${{ secrets.SECRET_KEY }}
            DATABASE_HOST=${{ secrets.DATABASE_HOST }}
            DATABASE_PORT=${{ secrets.DATABASE_PORT }}
            DATABASE_NAME=${{ secrets.DATABASE_NAME_TEST }}
            DATABASE_USERNAME=${{ secrets.DATABASE_USERNAME_TEST }}
            DATABASE_PASSWORD=${{ secrets.DATABASE_PASSWORD_TEST }}
            DATABASE_NAME_UNITTEST=${{ secrets.DATABASE_NAME_UNITTEST }}
            EOF

            # 仮想環境を作成
            pipenv --python 3

            # Pipfile.lock からパッケージをインストール
            pipenv sync

            # DB マイグレーション
            pipenv run python manage.py migrate

            # static ファイルの収集
            pipenv run python manage.py collectstatic --noinput

            # 単体テスト実行
            pipenv run python manage.py test --keepdb

            # index.cgi を作成
            cat << EOF > index.cgi
            #!$(pipenv --venv)/bin/python
            import sys

            import dotenv
            import pymysql

            # pipenv のパスを追加
            sys.path.insert(0, "$(pipenv --venv)/bin")

            # .env ファイルから環境変数を読み込む
            dotenv.load_dotenv()

            # pymysql で MySQL を使えるようにする
            pymysql.install_as_MySQLdb()

            from wsgiref.handlers import CGIHandler
            from django.core.wsgi import get_wsgi_application

            # アプリケーションを起動
            application = get_wsgi_application()
            CGIHandler().run(application)
            EOF

            # index.cgi のパーミッションを変更
            chmod 755 index.cgi

ステップ「FTP Deploy」では、FTP を使用してリポジトリの内容をエックスサーバーに転送します。

exclude で転送を除外するファイルおよびディレクトリを設定します。
ここでは .git/ などの「.」で始まるディレクトリと .editorconfig, .gitignore を除外しています。

転送したファイルの内容は、転送先の .ftp-deploy-sync-state.json というファイルに記録され、これによって内容が同期されます。
転送元でファイルが削除された場合はミラーリングにより転送先でも削除されますが、転送先にもともと存在していたファイルやディレクトリが勝手に削除されることはありません。

ステップ「SSH Remote Commands」では、SSH 接続によりエックスサーバー上でコマンドを実行します。
テキストファイルの出力には、ヒアドキュメントという機能を使用しています。
実行しているコマンドは以下の通りです。

  • .env ファイルに Django で使用するための環境変数を出力します。

  • pipenv 仮想環境を作成します。

  • pipenv にパッケージをインストールします。

  • Django の DB マイグレーションを行います。

  • Django の静的ファイルの収集を行います。

  • Django の単体テストを実行します。

  • index.cgi を作成します。

出力された index.cgi は、以下のような内容になります。

#!/home/xs******/.local/share/virtualenvs/test.***.com-***/bin/python
import sys

import dotenv
import pymysql

# pipenv のパスを追加
sys.path.insert(0, "/home/xs******/.local/share/virtualenvs/test.***.com-***/bin")

# .env ファイルから環境変数を読み込む
dotenv.load_dotenv()

# pymysql で MySQL を使えるようにする
pymysql.install_as_MySQLdb()

from wsgiref.handlers import CGIHandler
from django.core.wsgi import get_wsgi_application

# アプリケーションを起動
application = get_wsgi_application()
CGIHandler().run(application)

URI のリクエスト時、.htaccess により index.cgi に転送されます。
その際に pipenv 仮想環境の Python で Django アプリケーションを起動します。

ここでは pipenv run や pipenv shell を経由せず pipenv を実行しているため、.env ファイルの内容が自動で読み込まれません。
そのため、python-dotenv から手動で .env ファイルを読み込む必要があります。
また manage.py も経由していないため、PyMySQL の有効化をここにも記載する必要があります。

さて、ここまで完了したら、dev ブランチへプッシュします。
プッシュすると GitHub Actions が動くはずです。

GitHub Actions のログと、エックスサーバーのファイルマネージャを確認します。
デプロイが完了したら、テスト環境のサブドメインにアクセスしてみましょう。
アクセス制限で設定したユーザでログインし、ページが表示されたら成功です!

スーパーユーザを作成してデータを投入する

エックスサーバーに SSH 接続し、Django のスーパーユーザを作成します。

$ python manage.py createsuperuser

スーパーユーザを作成したら、テスト環境の admin サイトにログインします。
あなたのサイトに必要なデータを投入し、ページの表示を確認してください。

ステージング環境へデプロイする

テスト環境での動作確認が完了したら、ステージング環境へのデプロイを同様に実施します。

.github/workflows/staging-deploy.yml

# ステージング環境へデプロイする
name: Staging Deploy

on:
  # dev ブランチへプッシュされたとき
  push:
    branches:
      - dev

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3.5.3

      # FTP 経由でファイルをサーバへ転送する
      - name: FTP Deploy
        uses: SamKirkland/FTP-Deploy-Action@v4.3.4
        with:
          server: ${{ secrets.FTP_HOST }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          server-dir: ***.com/public_html/stage.***.com/
          exclude: |
            **/.**/**
            .editorconfig
            .gitignore

      # SSH 経由でコマンドを実行する
      - name: SSH Remote Commands
        uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.SSH_HOST }}
          port: ${{ secrets.SSH_PORT }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          passphrase: ${{ secrets.SSH_PASSPHRASE }}
          script: |
            cd ***.com/public_html/stage.***.com/

            # .env を作成
            cat << EOF > .env
            DJANGO_SETTINGS_MODULE=config.settings.config_stage
            SECRET_KEY=${{ secrets.SECRET_KEY }}
            DATABASE_HOST=${{ secrets.DATABASE_HOST }}
            DATABASE_PORT=${{ secrets.DATABASE_PORT }}
            DATABASE_NAME=${{ secrets.DATABASE_NAME_STAGE }}
            DATABASE_USERNAME=${{ secrets.DATABASE_USERNAME_STAGE }}
            DATABASE_PASSWORD=${{ secrets.DATABASE_PASSWORD_STAGE }}
            EOF

            # 仮想環境を作成
            pipenv --python 3

            # Pipfile.lock からパッケージをインストール
            pipenv sync

            # DB マイグレーション
            pipenv run python manage.py migrate

            # static ファイルの収集
            pipenv run python manage.py collectstatic --noinput

            # デプロイ設定のチェック
            pipenv run python manage.py check --deploy

            # index.cgi を作成
            cat << EOF > index.cgi
            #!$(pipenv --venv)/bin/python
            import sys

            import dotenv
            import pymysql

            # pipenv のパスを追加
            sys.path.insert(0, "$(pipenv --venv)/bin")

            # .env ファイルから環境変数を読み込む
            dotenv.load_dotenv()

            # pymysql で MySQL を使えるようにする
            pymysql.install_as_MySQLdb()

            from wsgiref.handlers import CGIHandler
            from django.core.wsgi import get_wsgi_application

            # アプリケーションを起動
            application = get_wsgi_application()
            CGIHandler().run(application)
            EOF

            # index.cgi のパーミッションを変更
            chmod 755 index.cgi

テスト環境と異なる点として、まず単体テストをスキップしています。
そして本番環境向けのセキュリティチェックを実施しています。

セキュリティチェックでは、セキュリティの設定に問題があった場合にログ出力で教えてくれます。
今回の手順を実施した後であれば問題は発生していないはずです。
興味があれば、config/settings/stage.py のセキュリティ設定を試しに無効化してみて、GitHub Actions のログをチェックしてみてください。

dev ブランチへプッシュして、テスト環境と同様に動作確認を実施します。

本番環境へデプロイする

ステージング環境での動作確認が完了したら、本番環境へデプロイします。

.github/workflows/production-deploy.yml

# 本番環境へデプロイする
name: Production Deploy

on:
  # master ブランチへプッシュされたとき
  push:
    branches:
      - master

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3.5.3

      # FTP 経由でファイルをサーバへ転送する
      - name: FTP Deploy
        uses: SamKirkland/FTP-Deploy-Action@v4.3.4
        with:
          server: ${{ secrets.FTP_HOST }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          server-dir: ***.com/public_html/
          exclude: |
            **/.**/**
            .editorconfig
            .gitignore

      # SSH 経由でコマンドを実行する
      - name: SSH Remote Commands
        uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.SSH_HOST }}
          port: ${{ secrets.SSH_PORT }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          passphrase: ${{ secrets.SSH_PASSPHRASE }}
          script: |
            cd ***.com/public_html/

            # .env を作成
            cat << EOF > .env
            DJANGO_SETTINGS_MODULE=config.settings.config_prod
            SECRET_KEY=${{ secrets.SECRET_KEY }}
            DATABASE_HOST=${{ secrets.DATABASE_HOST }}
            DATABASE_PORT=${{ secrets.DATABASE_PORT }}
            DATABASE_NAME=${{ secrets.DATABASE_NAME_PROD }}
            DATABASE_USERNAME=${{ secrets.DATABASE_USERNAME_PROD }}
            DATABASE_PASSWORD=${{ secrets.DATABASE_PASSWORD_PROD }}
            EOF

            # 仮想環境を作成
            pipenv --python 3

            # Pipfile.lock からパッケージをインストール
            pipenv sync

            # DB マイグレーション
            pipenv run python manage.py migrate

            # static ファイルの収集
            pipenv run python manage.py collectstatic --noinput

            # デプロイ設定のチェック
            pipenv run python manage.py check --deploy

            # index.cgi を作成
            cat << EOF > index.cgi
            #!$(pipenv --venv)/bin/python
            import sys

            import dotenv
            import pymysql

            # pipenv のパスを追加
            sys.path.insert(0, "$(pipenv --venv)/bin")

            # .env ファイルから環境変数を読み込む
            dotenv.load_dotenv()

            # pymysql で MySQL を使えるようにする
            pymysql.install_as_MySQLdb()

            from wsgiref.handlers import CGIHandler
            from django.core.wsgi import get_wsgi_application

            # アプリケーションを起動
            application = get_wsgi_application()
            CGIHandler().run(application)
            EOF

            # index.cgi のパーミッションを変更
            chmod 755 index.cgi

master ブランチへのプッシュ (マージ) により、デプロイが実施されます。

仕上げに、ステージング環境までと同様にスーパーユーザの作成とデータ投入を行ってください。

これで Django で開発したウェブサイトの公開ができました!
お疲れ様でした!


最後に

最後まで読んでくださり、ありがとうございました。

今回の内容で質問があったり、内容の過不足や不備、誤字脱字、脆弱性等を発見したりした場合は、ぜひコメントしてください!

この記事が気に入ったらサポートをしてみませんか?