はじめに
DjangoとVueとNginxを使って開発をしています。
開発環境から本番環境へのデプロイ時に手間取ったのでスムーズに移行できるような仕組みを作ろうと思いました。
まだまだ至らない点があると思います。もっとこうした方がいいなどのアドバイスがありましたらコメントで教えていただけると幸いです。
開発環境
- macOs Monterey ver12.1(M1)
- docker Engine 20.10.12
- docker-compose version 1.29.2
全体構成
- Dockerを使って環境を作成していきます
- 開発環境ではDBサーバーはDjangoのSQLiteを使用して、本番環境だけNginxのコンテナを使います。
手順
開発環境作成
docker-compose.dev.yml作成
まずは、開発環境用のdocker-composeファイルを作成していきます。
touch docker-compose.dev.yml
touch .env.dev
.env.devファイルは後で使います。
version: '3.7'
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
command: sh -c " python manage.py migrate && python manage.py runserver 0.0.0.0:8000 "
ports:
- 8000:8000
env_file:
- ./.env.dev
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
command: npm run serve
ports:
- 8080:8080
env_file:
- ./.env.dev
それに合わせてフォルダ作成を行います。
backendフォルダ作成
django用のフォルダとしてbackendフォルダを作成して、プロジェクトを作成します。
mkdir backend
touch .backend/requirements.dev.txt
django-admin startproject config ./backend/
開発環境で立ち上げた時にアクセスするために設定ファイルを書き換えます。
- ALLOWED_HOSTS = []
+ ALLOWED_HOSTS = ["*"]
django==4.0.5
django-webpack-loader==0.7.0
pytz==2022.1
backend用のDockerfileを作成します。
FROM python:3.10-alpine
ENV APP_HOME=/home/app/web
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
RUN pip install --upgrade pip
COPY ./requirements.dev.txt .
RUN pip install -r requirements.dev.txt
COPY . .
frontendフォルダ作成
Vueプロジェクトを作成します。
インストール方法はマニュアルでお好みです。
vue2.XとVueRouter、Vuexを入れました。
vue create frontend .
その後作成したフォルダに移動して、webpack-bundle-trackerをインストールします。
バージョン0系と1系がありますが、1系だとうまくいかなかったので0系でインストールします。
cd frontend
npm install webpack-bundle-tracker@0.4.3
frontend用のDockerfileを作成します。
FROM node:14-alpine
# カレントワーキングディレクトリとして 'app' フォルダを指定する
WORKDIR /app
# `package.json` と `package-lock.json` (あれば)を両方コピーする
COPY package*.json ./
# プロジェクトの依存ライブラリをインストールする
RUN npm install
# カレントワーキングディレクトリ(つまり 'app' フォルダ)にプロジェクトのファイルやフォルダをコピーする
COPY . .
ここまででディレクトリ構成は以下になります。
.
├── .env.dev
├── backend
│ ├── Dockerfile.dev
│ ├── config
│ ├── manage.py
│ └── requirements.dev.txt
├── docker-compose.dev.yml
└── frontend
├── .gitignore
├── Dockerfile.dev
├── README.md
├── babel.config.js
├── jsconfig.json
├── node_modules
├── package-lock.json
├── package.json
├── public
├── src
└── vue.config.js
開発用の環境を立ち上げて動作確認
docker-compose -f docker-compose.dev.yml build
docker-compose -f docker-compose.dev.yml up -d
django
http://0.0.0.0:8000/
動作確認が終わったらコンテナを立ち下げます。
docker-compose -f docker-compose.dev.yml down -v
本番環境作成
次に本番環境用のファイルを作成していきます。
手順としては、先ほどの開発環境と同じです。
- docker-composeファイル作成
- Dockerfile作成
- 設定ファイル変更
docker-compose.prod.yml作成
touch docker-compose.prod.yml
touch .env.prod
さきほどの.env.devファイルを編集していきます。
DEBUG=True
SECRET_KEY=$x=-wc4hbdkfbd$who$tova=js7b19nb&tf=n)eq6txjz#du2m
#*の後にスペースを忘れない
ALLOWED_HOSTS=*
本番用の環境変数ファイルを編集します。
SQL_DATABASE=db_name
SQL_USER=user_name
SQL_PASSWORD=password
SQL_HOST=db
SQL_PORT=5432
POSTGRES_USER=user_name
POSTGRES_PASSWORD=password
POSTGRES_DB=db_name
DATABASE=postgres
本番用のdocker-composeファイルです。
version: '3.7'
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile.prod
command: sh -c "python manage.py collectstatic --no-input --clear && python manage.py makemigrations && python manage.py migrate && gunicorn config.wsgi:application --bind 0.0.0.0:8000 "
volumes:
- static_volume:/home/app/web/static
- media_volume:/home/app/web/media
- frontend_volume:/app
expose:
- 8000
env_file:
- ./.env.prod
depends_on:
db:
condition: service_started
frontend:
condition: service_completed_successfully
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.prod
command: npm run build
ports:
- 8080:8080
env_file:
- ./.env.prod
volumes:
- frontend_volume:/app
db:
image: postgres:12.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- ./.env.prod
web:
build:
context: ./nginx
dockerfile: Dockerfile.prod
volumes:
- static_volume:/home/app/web/static
- media_volume:/home/app/web/media
ports:
- 1317:80
depends_on:
- backend
volumes:
postgres_data:
static_volume:
media_volume:
frontend_volume:
Dockerfileを作成して編集していきます。
touch ./backend/Dockerfile.prod
touch ./frontend/Dockerfile.prod
backendフォルダ作成
本番環境用のDockerfile(backend)
FROM python:3.10-alpine
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir -p $APP_HOME
RUN mkdir -p $APP_HOME/static
RUN mkdir -p $APP_HOME/media
WORKDIR $APP_HOME
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install psycopg2 dependencies
RUN apk update \
&& apk add postgresql-dev gcc python3-dev musl-dev
RUN pip install --upgrade pip
COPY ./requirements.prod.txt .
RUN pip install -r requirements.prod.txt
COPY ./entrypoint.sh .
COPY . .
ENTRYPOINT ["/home/app/web/entrypoint.sh"]
Djangoは本番環境でDBサーバーと連携するため、DBサーバーの立ち上がりを待つ必要があります。
postgreSQL→backendの順番で動作するよう待機用のシェルを読み込みます。
#!/bin/sh
if [ "$DATABASE" = "postgres" ]
then
echo "Waiting for postgres..."
while ! nc -z $SQL_HOST $SQL_PORT; do
sleep 0.1
done
echo "PostgreSQL started"
fi
exec "$@"
新しくアプリを追加します。
python manage.py startapp vue
このアプリからVueのページを見に行くようにするための設定とDjango側で環境変数を読み込むための設定を行います。
INSTALLED_APPS = [
# 省略
# 3rd party
+ 'rest_framework',
+ 'webpack_loader',
# my-app
+ 'vue',
]
+ STATIC_ROOT = os.path.join(BASE_DIR, "static")
+ STATICFILES_DIRS = [
+ os.path.join('/app/dist')
+ ]
+ WEBPACK_LOADER = {
+ 'DEFAULT': {
+ 'BUNDLE_DIR_NAME': 'dist/',
+ 'STATS_FILE': os.path.join('/', 'app', 'webpack-stats.json')
+ }
+ }
- DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': BASE_DIR / 'db.sqlite3',
- }
- }
+ DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.postgresql_psycopg2',
+ 'NAME': os.environ.get("SQL_DATABASE"),
+ 'USER': os.environ.get("SQL_USER"),
+ 'PASSWORD': os.environ.get("SQL_PASSWORD"),
+ 'HOST': os.environ.get("SQL_HOST"),
+ 'PORT': os.environ.get("SQL_PORT"),
+ }
+ }
djangoでvueのページを表示するようにルーティングを追加します。
まずは、configのurls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('vue.urls')), #追加
]
vue用のurls.pyを作成
touch ./backend/vue/urls.py
vue用のurls.pyを編集する。
from django.urls import path
from . import views
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
]
次にviews.pyを編集する
from django.views.generic import TemplateView
class IndexView(TemplateView):
template_name = 'vue/index.html'
テンプレートフォルダとindex.htmlの作成
mkdir -p templates/vue
touch templates/vue/index.html
index.htmlの中身を記述する。
{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Board</title>
</head>
<body>
<h1>Vue JS</h1>
<div id="app"></div>
{% render_bundle 'app' %}
</body>
</html>
本番環境でnginxと連携するためにDjangoをgunicornで起動する必要があるため、ライブラリを追加していきます。
touch ./backend/requirements.prod.txt
作成したファイルを編集していきます。
django==4.0.5
psycopg2-binary==2.9.3
gunicorn==20.1.0
django-webpack-loader==0.7.0
djangorestframework==3.11.0
pytz==2022.1
frontendフォルダ作成
本番環境用のDockerfile(frontend)
FROM node:14-alpine
# カレントワーキングディレクトリとして 'app' フォルダを指定する
WORKDIR /app
# `package.json` と `package-lock.json` (あれば)を両方コピーする
COPY package*.json ./
# プロジェクトの依存ライブラリをインストールする
RUN npm install
# カレントワーキングディレクトリ(つまり 'app' フォルダ)にプロジェクトのファイルやフォルダをコピーする
COPY . .
ここでvuetifyを追加します。
マニュアルを選択しました。
lintのエラーが出ますが、無視して大丈夫です。
vue add vuetify
本番ではwebpackを使ってバンドルしたファイルをNginxから配信するためwebpackの設定を変更していきます。
const BundleTracker = require("webpack-bundle-tracker");
module.exports = {
publicPath: "/static/dist/",
outputDir: "./dist/dist/",
transpileDependencies: ["vuetify"],
chainWebpack: (config) => {
config
.plugin("BundleTracker")
.use(BundleTracker, [{ filename: "./webpack-stats.json" }]);
config.output.filename("bundle.js");
config.optimization.splitChunks(false);
config.resolve.alias.set("__STATIC__", "static");
config.devServer
.https(false)
.headers({ "Access-Control-Allow-Origin": ["*"] });
},
lintOnSave: false,
};
nginxフォルダ作成
本番環境ではwebサーバーとしてnginxを使用します。
そのためのDockerfileを作成します。
またnginx.confファイルの作成も行います。
mkdir nginx
touch ./nginx/nginx.conf
touch ./Dockerfile.prod
FROM nginx:latest
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d
nginx.confファイルにwebサーバの設定を書いていきます。
upstream project {
server backend:8000;
}
server {
listen 80;
location / {
proxy_pass http://project;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /static/ {
alias /home/app/web/static/;
}
location /media/ {
alias /home/app/web/media/;
}
}
ここまででフォルダ構成は以下になります。
.
├── .env.dev
├── .env.prod
├── backend
│ ├── Dockerfile.dev
│ ├── Dockerfile.prod
│ ├── config
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── entrypoint.sh
│ ├── manage.py
│ ├── requirements.dev.txt
│ ├── requirements.prod.txt
│ └── vue
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── templates
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── docker-compose.dev.yml
├── docker-compose.prod.yml
├── frontend
│ ├── .gitignore
│ ├── Dockerfile.dev
│ ├── Dockerfile.prod
│ ├── README.md
│ ├── babel.config.js
│ ├── jsconfig.json
│ ├── node_modules
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── src
│ │ ├── App.vue
│ │ ├── assets
│ │ ├── components
│ │ ├── main.js
│ │ ├── plugins
│ │ ├── router
│ │ ├── store
│ │ └── views
│ └── vue.config.js
├── nginx
├── Dockerfile.prod
└── nginx.conf
本番環境の準備が整ったので、起動して動作確認していきます。
docker-compose -f docker-compose.prod.yml build
docker-compose -f docker-compose.prod.yml up -d
以下のURLにアクセスして表示されるか確認します。
vuetifyが表示されると成功です。
localhost:1317/
動作確認できたら、立ち下げます。
docker-compose -f docker-compose.dev.yml down -v
開発環境と本番環境を切り替えて使用する
最後に開発環境と本番環境で環境変数を切り替えられるようにします。
手順としては、以下の通りです。
- docker-compose用の.env編集
- docker-compose.dev.yml編集
- Djangoのsettingsを分ける
- Vueの.env作成
docker-compose用の.envファイルを編集していきます。
すでにプロジェクトディレクトリ直下に.env.devと.env.prodファイルがあるのでその2つを編集していきます。
DEBUG=True
SECRET_KEY=$x=-wc4hbdkfbd$who$tova=js7b19nb&tf=n)eq6txjz#du2m
ALLOWED_HOSTS=*
DJANGO_SETTINGS_MODULE=config.settings.dev
NODE_ENV=development
DEBUG=False
SECRET_KEY=$x=-wc4hbdkfbd$who$tova=js7b19nb&tf=n)eq6txjz#du2m
ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
SQL_DATABASE=db_name
SQL_USER=user_name
SQL_PASSWORD=password
SQL_HOST=db
SQL_PORT=5432
POSTGRES_USER=user_name
POSTGRES_PASSWORD=password
POSTGRES_DB=db_name
DATABASE=postgres
DJANGO_SETTINGS_MODULE=config.settings.prod
NODE_ENV=production
docker-compose.dev.yml編集
webpack-loaderでファイルが見れるようにvolumesを追加します。
version: '3.7'
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
command: sh -c " python manage.py migrate && python manage.py runserver 0.0.0.0:8000 "
volumes:
- frontend_volume:/app
ports:
- 8000:8000
env_file:
- ./.env.dev
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
command: npm run serve
volumes:
- frontend_volume:/app
ports:
- 8080:8080
env_file:
- ./.env.dev
volumes:
frontend_volume:
Djangoのsettingsを分ける
手順
- configフォルダ直下にsettingsフォルダを新規作成
- settings.pyファイルをbase.pyに名前変更してsettingsフォルダに移動
- 新規にdev.pyとprod.pyと__init__.pyファイルの3つをsettingsフォルダ直下に作成
configのフォルダ構成は以下を参考にしてください。
__pycache__以下はなくても問題ありません。
./config
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-310.pyc
│ └── settings.cpython-310.pyc
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
./config
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-310.pyc
│ ├── settings.cpython-310.pyc
│ ├── urls.cpython-310.pyc
│ └── wsgi.cpython-310.pyc
├── settings
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ ├── base.cpython-310.pyc
│ │ └── dev.cpython-310.pyc
│ ├── base.py
│ ├── dev.py
│ └── prod.py
├── urls.py
└── wsgi.py
base.pyを変更します。
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 3rd party
'rest_framework',
'webpack_loader',
# my-app
'vue',
]
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',
]
ROOT_URLCONF = 'config.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 = 'config.wsgi.application'
# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
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',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_DIRS = [
os.path.join('/app/dist')
]
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'dist/',
'STATS_FILE': os.path.join('/', 'app', 'webpack-stats.json')
}
}
dev.pyを編集します。
from .base import *
import os
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get("DEBUG") == "True" #DEBUGはbool型なので文字列の判定結果を格納する
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS").split(" ")
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
prod.pyを編集します。
from .base import *
import os
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get("DEBUG") == "True" #DEBUGはbool型なので文字列の判定結果を格納する
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS").split(" ")
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.environ.get("SQL_DATABASE"),
'USER': os.environ.get("SQL_USER"),
'PASSWORD': os.environ.get("SQL_PASSWORD"),
'HOST': os.environ.get("SQL_HOST"),
'PORT': os.environ.get("SQL_PORT"),
}
}
settingsファイルのディレクトリが変わったので、少し修正します。
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev')
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev')
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev')
Vueの.env作成
frontendフォルダ直下に.env.development.localと.env.production.localを作成します。
.env.localと.env.*.localは.gitignoreにデフォルトで含まれているのでgithubに共有されることはありません。
BASE_URL=http://0.0.0.0:8080/
BASE_URL=/static/dist/
設定したBASE_URLを読み込めるようにvue.config.jsファイルを修正します。
- publicPath: "/static/dist/",
+ publicPath: process.env.BASE_URL,
最終的にディレクトリ構成は以下になります。
./frontend/node_modulesは非表示にしています。
.
├── .env.dev
├── .env.prod
├── backend
│ ├── Dockerfile.dev
│ ├── Dockerfile.prod
│ ├── config
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ ├── asgi.py
│ │ ├── settings
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── entrypoint.sh
│ ├── manage.py
│ ├── requirements.dev.txt
│ ├── requirements.prod.txt
│ └── vue
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── templates
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── docker-compose.dev.yml
├── docker-compose.prod.yml
├── frontend
│ ├── .env.development.local
│ ├── .env.production.local
│ ├── .gitignore
│ ├── Dockerfile.dev
│ ├── Dockerfile.prod
│ ├── README.md
│ ├── babel.config.js
│ ├── jsconfig.json
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── src
│ │ ├── App.vue
│ │ ├── assets
│ │ ├── components
│ │ ├── main.js
│ │ ├── plugins
│ │ ├── router
│ │ ├── store
│ │ └── views
│ └── vue.config.js
└── nginx
├── Dockerfile.prod
└── nginx.conf
開発環境と本番環境の動作確認を行います。
docker-compose -f docker-compose.dev.yml build
docker-compose -f docker-compose.dev.yml up -d
django
http://0.0.0.0:8000/
http://0.0.0.0:8000/admin
vuetifyの画面が表示されれば正常です。
確認できたら、立ち下げます。
docker-compose -f docker-compose.dev.yml down -v
そして、本番環境で起動します。
docker-compose -f docker-compose.prod.yml build
docker-compose -f docker-compose.prod.yml up -d
django
http://localhost/1317
確認できたら、立ち下げます。
docker-compose -f docker-compose.prod.yml down -v
参考サイト
開発環境と本番環境を分けるために以下のサイトを参考にさせていただきました。
ありがとうございました。