Docker composeの復習がてらSPAサイトの雛形コンテナ構成を最速ビルドしてみる
可能な限り初期ファイル構成はシンプルかつ最小限で。
ディレクトリ&コンテナ構成
docker_spa/
┣━ back/
┃ ┣━ scripts/
┃ ┃ ┣━ django-appstart.sh # Django新規アプリ作成用スクリプト
┃ ┃ ┣━ django-settings.sh # Djangoセットアップ用スクリプト
┃ ┃ ┗━ start.sh # コンテナ起動関連スクリプト
┃ ┣━ Dockerfile # バックエンド用Dockerfile
┃ ┗━ requirements.txt # pythonパッケージ一覧
┣━ front/
┃ ┣━ scripts/
┃ ┃ ┣━ start.sh # 「settings.py」修正用スクリプト
┃ ┃ ┗━ vue-settings.sh # vue用スクリプト
┃ ┗━ Dockerfile # フロントエンド用Dockerfile
┣━ web/
┃ ┣━ conf/
┃ ┃ ┗━ app_nginx.conf # アプリ用Nginx設定ファイル
┃ ┣━ logs/nginx
┃ ┃ ┗━ access.log # アクセスログ
┃ ┃ ┗━ error.log # エラーログ
┃ ┣━ Dockerfile # webサーバー用Dockerfile
┃ ┗━ uwsgi_params # アプリサーバー設定ファイル
┃
┗━ docker-compose.yml
docker-compose.yml
version: '3.7'
services:
web:
container_name: web-container
build:
context: ./
dockerfile: ./web/Dockerfile
restart: unless-stopped # コンテナが異常停止した場合は再起動する
ports:
- "8000:8000"
environment:
TZ: "Asia/Tokyo"
volumes:
- ./web/conf:/etc/nginx/conf.d
- ./web/logs/nginx/:/var/log/nginx/
- ./web/uwsgi_params:/etc/nginx/uwsgi_params
- ./web/static:/static
- django_statics:/var/www/vhosts/localhost/static:ro
networks:
- django_net
depends_on:
- back
back:
container_name: back-container
build:
context: ./back
dockerfile: ./Dockerfile
command: 'sh /code/scripts/start.sh'
restart: unless-stopped
volumes:
- ./back:/code
- ./back/app/static:/static
expose:
- "8001"
networks:
- django_net
front:
container_name: front-container
build:
context: ./front
dockerfile: ./Dockerfile # ./front/Dockerfileを参照
ports:
- 8080:8080
expose:
- "3000"
command: 'sh /code/scripts/start.sh'
volumes:
- ./front:/code
- ./static:/static
networks:
- django_net
depends_on:
- back
networks:
django_net:
driver: bridge
volumes:
django_statics:
driver: local
詳細な導線は異なるところはあると思いますが、上記がComposeのコンテナ構成イメージです。
- ページアクセスのリクエストをwebサーバーコンテナ内のNginxがプロキシサーバーとして中継
- バックエンドコンテナ内のアプリサーバーのuWSGIでバックエンドとNginxとの疎通
- POSTリクエストをAPIサーバーとしてDjango(REST Framework)で処理
- Vueのaxiosのプロキシサーバーと連結してレスポンスオブジェクトをVueの非同期で受け取る
こんな流れで最小限のSPA環境をビルドします。
今回はwebアプリとしてVueからしか繋いでないのに無駄に複雑な構成になってますが、専用コンテナをcomposeに追加してそこからのリクエストをAPIサーバーに処理してもらってNginx経由でアクセスを振り分けてもらえば将来的にネイティブアプリやデスクトップアプリなどクロスプラットフォーム対応した際にもコンテナ同士の構造を意識しなくても良くなる・・・ハズ・・・。
バックエンド側
APIサーバー処理用のバックエンド側のコンテナ構成(Dockerfile)
FROM python:3.8
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
EXPOSE 8000
# 開発ディレクトリ設定
ENV WORKDIR /code
WORKDIR ${WORKDIR}
COPY ./scripts/ ${WORKDIR}/
# コンテナ内でパッケージを一括インストール
COPY requirements.txt ${WORKDIR}
RUN pip install --upgrade pip && pip install -r requirements.txt && \
pip install coreapi django-cors-headers python-jose
COPY . ${WORKDIR}/
コンテナ起動関連スクリプト
- 初回ビルド時にappフォルダ&新規プロジェクトを作成
- 同時に各種セットアップのスクリプトを実行
- migrationとuWSGIサーバーを起動(※2回目以降はここのみ実行)
#!/bin/bash
set 'echo -e -o pipefail'
unix_today=$(date +'%s')
unix_today=$((unix_today+32400))
jst_ymd_today=$(date '+%Y/%m/%d %H:%M:%S' --date "@$unix_today")
# メインプロジェクトフォルダが無ければセットアップ実行
if [ ! -d /code/app ]; then
echo "${jst_ymd_today}|プロジェクト初期化"
mkdir app && cd app
django-admin startproject config .
# djangoセットアップ&サーバー起動
bash /code/scripts/django-settings.sh
bash /code/scripts/django-appstart.sh main
echo "${jst_ymd_today}|初期設定完了"
# python manage.py createsuperuser
else
echo "${jst_ymd_today}|コンテナ更新"
fi
cd app
python manage.py makemigrations
python manage.py migrate
# python manage.py collectstatic
# 静的ファイルディレクトリをstatic指定ディレクトリに集約コピー(--noinputで対話プロンプトを無視)
python manage.py collectstatic --noinput
# configアプリuWSGIに接続。「--py-autoreload 1」でファイル等に変更があった際は自動リロード
uwsgi --socket :8001 --module config.wsgi --py-autoreload 1 --logto /tmp/uwsgi.log
# python manage.py runserver 0.0.0.0:8001
Djangoセットアップ用スクリプト
- 「config/settings.py」の各種セットアップ
- タイムゾーンと言語を日本語対応
- REST Framework設定を追記
- staticディレクトリ設定
- 「config/url.py」のセットアップ
- 静的ファイル用「template」ディレクトリ作成
#!/bin/sh
# シェル実行が失敗したら終了してエラー出力
set 'echo -e -o pipefail'
# ルートディレクトリ取得
ROOT_DIR=$(cd $(dirname $0)/..;pwd)
echo ${ROOT_DIR}
# 各種モジュールインポートを追記
if ! grep -q "import os" "${ROOT_DIR}/app/config/settings.py" ;then
sed -i -e "s/from pathlib import Path/from pathlib import Path \nimport os\nimport json\nfrom six.moves.urllib import request\nfrom cryptography.x509 import load_pem_x509_certificate\nfrom cryptography.hazmat.backends import default_backend/" ${ROOT_DIR}/app/config/settings.py
fi
# BASE_DIR変更
sed -i -e "s/BASE_DIR = Path(__file__).resolve().parent.parent/BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))/" ${ROOT_DIR}/app/config/settings.py
# INSTALLED_APPSにrest_frameworkとrest_framework_jwtとcorsheadersを追加
if ! grep -q "'rest_framework'," "${ROOT_DIR}/app/config/settings.py" ;then
sed -i -e "s/'django.contrib.staticfiles',/'django.contrib.staticfiles',\n 'rest_framework', \n 'rest_framework_jwt', \n 'corsheaders',/" ${ROOT_DIR}/app/config/settings.py
fi
# MIDDLEWAREにcorsheadersミドルウェアを追記
if ! grep -q "'corsheaders.middleware.CorsMiddleware'," "${ROOT_DIR}/app/config/settings.py" ;then
sed -i -e "s/'django.middleware.common.CommonMiddleware',/'corsheaders.middleware.CorsMiddleware', \n 'django.middleware.common.CommonMiddleware', \n/" ${ROOT_DIR}/app/config/settings.py
fi
# テンプレートディレクトリ設定
sed -i -e "s/'DIRS': \[\],/'DIRS': \[os.path.join(BASE_DIR, \"app\/templates\")\],/" ${ROOT_DIR}/app/config/settings.py
# 言語をjaに変更
sed -i -e "s/LANGUAGE_CODE = 'en-us'/LANGUAGE_CODE = 'ja'/" ${ROOT_DIR}/app/config/settings.py
# タイムゾーンをAsia/Tokyoに変更
sed -i -e "s/TIME_ZONE = 'UTC'/TIME_ZONE = 'Asia\/Tokyo'/" ${ROOT_DIR}/app/config/settings.py
# DATEBASEパス変更
sed -i -e "s/'NAME': BASE_DIR \/ 'db.sqlite3',/'NAME': BASE_DIR+'\/app\/db.sqlite3',/" ${ROOT_DIR}/app/config/settings.py
# ALLOWED_HOSTSにlocalhostと127.0.0.1追加
sed -i -e "s/ALLOWED_HOSTS = \[\]/ALLOWED_HOSTS = \[\n '127.0.0.1', \n 'localhost', \]/" ${ROOT_DIR}/app/config/settings.py
# STATIC_ROOT追記
sed -i -e "s/STATIC_URL = '\/static\/'/STATIC_URL = '\/static\/'\nSTATIC_ROOT = os.path.join(BASE_DIR, 'static')/" ${ROOT_DIR}/app/config/settings.py
# REST_FRAMEWORK設定追記
if ! grep -q "REST_FRAMEWORK = {" "${ROOT_DIR}/app/config/settings.py" ;then
echo "# REST_FRAMEWORK設定
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
}
# クロスオリジン許可
CORS_ORIGIN_WHITELIST = (
'http://localhost:8080',
)
" >> ${ROOT_DIR}/app/config/settings.py
fi
# importにincludeモジュールを追加&napp_name設定
sed -i -e "s/from django.urls import path/from django.urls import path, include \nfrom rest_framework.documentation import include_docs_urls\n/" ${ROOT_DIR}/app/config/urls.py
# APIドキュメントルート追加
sed -i -e "s/path('admin\/', admin.site.urls),/path('admin\/', admin.site.urls),\n path('docs\/', include_docs_urls(title='API Document')),\n/" ${ROOT_DIR}/app/urls.py
# templates&staticフォルダ階層作成
mkdir ${ROOT_DIR}/app/templates
mkdir ${ROOT_DIR}/app/static && mkdir ${ROOT_DIR}/app/static/css ${ROOT_DIR}/app/static/js ${ROOT_DIR}/app/static/images
# base.html作成
cat > ${ROOT_DIR}/app/templates/base.html << "EOF"
<!DOCTYPE html>
{% load tz %}
{% load static %}
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title>
{% block style %}{% endblock %}
</head>
<body>
{% if messages %}
<ul class="pl-0 ml-3">
{% for message in messages %}
<li class="alert alert-{{ message.tags }}">{{ message }}<button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button></li>
{% endfor %}
</ul>
{% endif %}
{% block content %}
{% endblock %}
{% block script %}{% endblock %}
</body>
</html>
EOF
# index.html作成
cat > ${ROOT_DIR}/app/templates/index.html << "EOF"
{% extends "base.html" %}
{% load static %}
{% block title %}ページタイトル{% endblock %}
{% block style %}
{% endblock %}
{% block breadcrumb %}
{% endblock %}
{% block content %}
インデックスページのコンテンツを表示中
{% endblock %}
{% block script %}
{% endblock %}
EOF
# template.html作成
cat > ${ROOT_DIR}/app/templates/template.html << EOF
{% extends "base.html" %}
{% load static %}
{% block title %}ページタイトル{% endblock %}
{% block style %}
{% endblock %}
{% block breadcrumb %}
{% endblock %}
{% block content %}
コンテンツテンプレートです
{% endblock %}
{% block script %}
{% endblock %}
EOF
pythonパッケージ一覧
Django==3.2
psycopg2-binary>=2.8
scrapy-djangoitem==1.1.1
scrapy==2.4
scrapyd==1.2.0
djangorestframework==3.12.4
djangorestframework-jwt==1.11.0
django-cors-headers==3.7.0
django-webpack-loader==1.0.0
uwsgi==2.0.19.1
markdown==3.3.4
django-filter==2.4.0
Pygments==2.9.0
新規アプリ作成用スクリプト
#!/bin/sh
# シェル実行が失敗したら終了してエラー出力
set -e -o pipefail
# 実行ディレクトリ取得
ROOT_DIR=$(cd $(dirname $0)/..;pwd)
echo $ROOT_DIR
# "新規アプリ作成"
# 新規ディレクトリ&アプリ作成
mkdir $ROOT_DIR/app/${1}
django-admin startapp ${1} $ROOT_DIR/app/${1}
# INSTALLED_APPSに新規アプリを追加
sed -i -e "s/'rest_framework',/'rest_framework', \n '${1}', /" ${ROOT_DIR}/app/config/settings.py
# websiteルーティング追加
sed -i -e "s/\]/ path('${1}\/', include('${1}.urls')),\n\]/" ${ROOT_DIR}/app/config/urls.py
# APP_NAME/urls.py作成
cat > $ROOT_DIR/app/${1}/urls.py << EOF
from django.urls import include, path
# from . import views
from .views import *
app_name = '${1}'
urlpatterns = [
path('api/jp', JPTestAPI.as_view()),
path('api/us', USTestAPI.as_view()),
path('api/br', BRTestAPI.as_view()),
path('', index, name='index'),
]
EOF
# APP_NAME/views.py修正
cat > $ROOT_DIR/app/${1}/views.py << "EOF"
from uuid import uuid4
from urllib.parse import urlparse
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
from django.views.decorators.http import require_POST, require_http_methods
from django.shortcuts import render
from django.utils import timezone
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import ListView, TemplateView, DetailView
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework import authentication, permissions
from rest_framework import status, viewsets, filters
from rest_framework import permissions
from rest_framework.response import Response
# from django.db.models import Q
import pytz
from datetime import datetime
# REST API 通信テスト用
class JPTestAPI(APIView):
permission_classes = (permissions.AllowAny,)
def get(self, request, format=None):
# return Response(data={'status': dt_now}, status=status.HTTP_200_OK)
jp = pytz.timezone('Asia/Tokyo')
return JsonResponse(data={'status': datetime.now(tz=jp)}, status=status.HTTP_200_OK)
class USTestAPI(APIView):
permission_classes = (permissions.AllowAny,)
def get(self, request, format=None):
us = pytz.timezone('US/Eastern')
return JsonResponse(data={'status': datetime.now(tz=us)}, status=status.HTTP_200_OK)
class BRTestAPI(APIView):
permission_classes = (permissions.AllowAny,)
def get(self, request, format=None):
br = pytz.timezone('Brazil/East')
return JsonResponse(data={'status': datetime.now(tz=br)}, status=status.HTTP_200_OK)
def index(request):
return render(request, 'index.html'
EOF
# "テンプレート作成"
# templates&staticフォルダ階層作成
mkdir $ROOT_DIR/app/templates/${1}
# "「config/urls.py」にルーティングを追加"
sed -i -e "s/]/ path('${1}', include('${1}.urls')),\n]/" $ROOT_DIR/config/urls.py
初回実行時に「main」ディレクトリを作成するスクリプトですが、他にも新規でアプリディレクトリを追加したい場合は以下のコマンドで APPNAME を好きなアプリ名で実行するとスクリプトが実行され、新規ディレクトリが作成されます
$ docker-compose run back bash /code/scripts/django-appstart.sh APPNAME
- 新規ディレクトリにアプリファイル一覧を作成
- 「app/config/settings.py」にアプリ名設定追記
- 「app/config/urls.py」にアプリルーティング追記
- 「app/templates/」に新規アプリ名ディレクトリを作成
- 「views.py」と「urls.py」に設定追記
フロントエンド側
webアプリとしてのフロントエンド側のコンテナ構成
FROM node:16.2.0
# 開発ディレクトリ設定
ENV WORKDIR /code
WORKDIR ${WORKDIR}
COPY ./scripts/ ${WORKDIR}/
# コンテナ内でパッケージを一括インストール
RUN npm update npm && npm install -g @vue/cli && \
npm install -g @vue/cli-init && npm install -S axios
COPY . ${WORKDIR}/
コンテナ起動関連スクリプト
- 初回ビルド時は新規Vueプロジェクト作成コマンドを催促
- 初回以降にappディレクトリ作成済の場合は各種スクリプト実行
#!/bin/bash
set 'echo -e -o pipefail'
unix_today=$(date +'%s')
unix_today=$((unix_today+32400))
jst_ymd_today=$(date '+%Y/%m/%d %H:%M:%S' --date "@$unix_today")
app_name='app'
# プロジェクトディレクトリが存在していればサーバー起動
if [ -d /code/${app_name} ]; then
# vueプロジェクト設定済みではなかったら
if [ ! -f /code/${app_name}/vue.config.js ]; then
echo "${jst_ymd_today} | Vue directory set up"
# vueテンプレート作成
bash /code/scripts/vue-settings.sh
else
echo "${jst_ymd_today} | Start the project server"
# プロジェクトディレクトリに移動してサーバー起動
cd ${app_name}
npm run serve
fi
else
echo "「${jst_ymd_today}」|Create a new project"
#***********************************************************************
echo "Vue axios設定ファイル作成"
#***********************************************************************
mkdir /code/${app_name}
if [ ! -f /code/${app_name}/vue.config.js ]; then
cat > /core/${app_name}/vue.config.js << "EOF"
module.exports = {
devServer: {
proxy: {
'^/api/': {
target: 'http://localhost:8000',
logLevel: 'debug',
pathRewrite: { "^/api/": "/api/" }
}
}
}
}
EOF
fi
echo "-----------------------------------------------------------"
echo "Run the command 'docker-compose run front vue create ${app_name} .' on the host machine"
echo "------------------------------------------------------------"
fi
Vue.js設定用スクリプト
- 「HelloWorld.vue」にテスト用コード追記
- 「main.js」にaxiosとrourer設定追記
- 「router.js」作成
- 「components」内にルーティングテンプレートファイル作成
#!/bin/bash
set 'echo -e -o pipefail'
# ルートディレクトリ取得
ROOT_DIR=$(cd $(dirname $0)/..;pwd)
app_name='app'
#************************************************************************************************
echo "「HelloWorld.vue」修正"
#************************************************************************************************
sed -i -e "s/<h1>{{ msg }}<\/h1>/<h1>{{ msg }}<\/h1>\n<h2>現在の時間:{{ result }}<\/h2>\n<button @click=\"getAPI()\">クリック!<\/button>/" ${ROOT_DIR}/${app_name}/src/components/HelloWorld.vue
sed -i -e "N;s/}\n<\/script>/, data () {\n return {\n result: 'No Result',\n url: 'http:\/\/localhost:8000\/api\/'\n }\n },\n methods: {\n getAPI () {\n this.\$axios.get(this.url).then(response => {\n this.result = response.data.status\n })\n }\n }\n}\n<\/script>/" ${ROOT_DIR}/${app_name}/src/components/HelloWorld.vue
#************************************************************************************************
echo "main.js修正"
#************************************************************************************************
cat > ${ROOT_DIR}/${app_name}/src/main.js << "EOF"
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
import router from './router.js'
Vue.config.productionTip = false
Vue.prototype.$axios = axios
new Vue({
router,
render: h => h(App),
}).$mount('#app')
EOF
#***********************************************************
echo "コンテンツテンプレート作成"
#**********************************************************
cat > ${ROOT_DIR}/${app_name}/src/router.js << "EOF"
import Vue from 'vue'
import Router from 'vue-router'
import top from '@/components/top'
import page2 from '@/components/page2'
import page3 from '@/components/page3'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
component: top
},
{
path: '/page2',
component: page2
},
{
path: '/page3',
component: page3
}
]
})
EOF
cat > ${ROOT_DIR}/${app_name}/src/components/top.vue << "EOF"
<template>
<div>
<p>ここはトップページです。</p>
<h2>現在の日本時間:{{ result }}</h2>
<button @click="getAPI()">クリック!</button>
</div>
</template>
<script>
export default {
name: 'jp-time',
props: {
msg: String
},
data () {
return {
result: '不明',
url: 'http://localhost:8000/api/jp'
}
},
methods: {
getAPI () {
this.$axios.get(this.url).then(response => {
this.result = response.data.status
})
}
}
}
</script>
<style>
</style>
EOF
cat > ${ROOT_DIR}/${app_name}/src/components/page2.vue << "EOF"
<template>
<div>
<p>ここは2ページ目です。</p>
<h2>現在のアメリカ時間:{{ result }}</h2>
<button @click="getAPI()">クリック!</button>
</div>
</template>
<script>
export default {
name: 'us-time',
props: {
msg: String
},
data () {
return {
result: '不明',
url: 'http://localhost:8000/api/us'
}
},
methods: {
getAPI () {
this.$axios.get(this.url).then(response => {
this.result = response.data.status
})
}
}
}
</script>
EOF
cat > ${ROOT_DIR}/${app_name}/src/components/page3.vue << "EOF"
<template>
<div>
<p>ここは3ページ目です。</p>
<h2>現在のブラジル時間:{{ result }}</h2>
<button @click="getAPI()">クリック!</button>
</div>
</template>
<script>
export default {
name: 'br-time',
props: {
msg: String
},
data () {
return {
result: '不明',
url: 'http://localhost:8000/api/br'
}
},
methods: {
getAPI () {
this.$axios.get(this.url).then(response => {
this.result = response.data.status
})
}
}
}
</script>
EOF
#*********************************************************
# "「App.vue」修正"
#*********************************************************
cat <<EOF > ${ROOT_DIR}/${app_name}/src/App.vue
<template>
<div id="app">
<div id="header">
<!--リンクタグを生成します。-->
<router-link to="/">top</router-link>41
<router-link to="/page2">2</router-link>
<router-link to="/page3">3</router-link>
</div>
<router-view></router-view>
</div>
</template>
<!--コンポーネントの名前を定義します。-->
<script>
export default {
name: 'App'
}
</script>
<!--スタイルの指定をします-->
<style>
body {
margin: 0;
}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#header {
height: 40px;
background: white;
box-shadow: 0px 3px 3px rgba(0,0,0,0.1);
display: flex;
justify-content: center;
align-items: center;
}
#header a {
text-decoration: none;
color: #2c3e50;
margin: 0 10px;
padding: 3px 10px;
background: #5ccebf;
}
</style>
EOF
Webサーバー
すべてのアクセスをさばくプロキシサーバー役のコンテナ構成
ROM nginx:1.11.7
RUN mkdir /var/www
RUN mkdir /var/www/app
RUN mkdir /var/www/static
アプリ用Nginxファイル
upstream back {
ip_hash;
server back:8001; # uWSGIでDjangoとnginxとが通信するためのポート
server front:3000; # Vueとnginxとが通信するためのポート
}
server {
listen 8000; # 待ち受けポート
server_name 127.0.0.1;
charset utf-8;
client_header_buffer_size 1k;
large_client_header_buffers 8 32k;
add_header Strict-Transport-Security 'max-age=31536000';
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
error_page 500 502 503 504 /50x.html;
location /static {
alias /var/www/static;
}
# バックエンドサーバー
location / {
include /etc/nginx/uwsgi_params;
uwsgi_pass back; # バックエンド
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
# バックエンド adminサーバー
location /admin/ {
include /etc/nginx/uwsgi_params;
uwsgi_pass back;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location = /50x.html {
root /usr/share/nginx/html;
}
client_max_body_size 75M;
}
server_tokens off;
uWSGI設定ファイル
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
コンテナ起動
まずコンテナ全体を起動。
$ cd docker_spa
$ docker-compose up -d
バックエンドにappディレクトリが作成&Djangoアプリ全体の設定ディレクトリ「config」とメインアプリディレクトリの「main」が作成されます。
webサーバー側のNginxも立ち上がるので http:localhost:8000
にアクセスで「インデックスページのコンテンツを表示中」のテキストが表示されていればDjangoとNginxの疎通はOK
$ docker-compose run front vue create app .
? Please pick a preset: Default (Vue 2) ([Vue 2] babel, eslint)
? Pick the package manager to use when installing dependencies: NPM
続いて上記コマンドで新規Vueプロジェクトは初期設定の対話コマンドがあるので「 Vue2 」と「 npm 」を選択してインストールします。
結構時間かかりますがインストールが完了したら再度 docker-compose -d
でビルドするとフロント側のセットアップスクリプトが実行されます
docker_spa/
┣━ back/
┃ ┣━ app/
┃ ┃ ┣━ config/
┃ ┃ ┣━ main/
┃ ┃ ┣━ static/
┃ ┃ ┣━ templates/
┃ ┃ ┣━ db.sqlite3
┃ ┃ ┗━ manage.py
┃ ┣━ scripts/
┃ ┣━ Dockerfile
┃ ┗━ requirements.txt
┣━ front/
┃ ┣━ app/
┃ ┃ ┣━ node_modules
┃ ┃ ┣━ public/
┃ ┃ ┣━ src/
┃ ┃ ┣━ babel.config.js
┃ ┃ ┣━ package-lock.json
┃ ┃ ┣━ package.json
┃ ┃ ┗━ vue.config.js # proxy設定ファイル
┃ ┣━ scripts/
┃ ┣━ Dockerfile
┃ ┣━ package-lock.json
┃ ┗━ package.json
┣━ web/
┃ ┣━ conf/
┃ ┃ ┗━ app_nginx.conf
┃ ┣━ logs/nginx
┃ ┃ ┗━ access.log
┃ ┃ ┗━ error.log
┃ ┣━ Dockerfile
┃ ┗━ uwsgi_params
┃
┗━ docker-compose.yml
上手くいけば上記の構成が出来上がってると思います。
http:localhost:8080
にアクセスするとVueのトップページが表示されます。
「top」「2」「3」でページが切り替わり、「クリック!」ボタンをクリックするとバックエンドとの通信を行い、現在の時刻を表示します。