タグ:Django | Docker | REST Framework | Vue.js
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
version: '3.7'
services:
web:
container_name: web-container
build:
context: ./
dockerfile: ./web/Dockerfile
restart: always
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
- static_volume:/usr/src/app/static
- media_volume:/usr/src/app/media
networks:
- django_net
depends_on:
- back
back:
container_name: back-container
build:
context: ./back
dockerfile: ./Dockerfile
command: 'sh /code/scripts/start.sh'
restart: always
volumes:
- ./back:/usr/src/app
- static_volume:/usr/src/app/static
- media_volume:/usr/src/app/media
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
- ./front:/app/:cached
- ./front/node_modules:/app/node_modules
networks:
- django_net
depends_on:
- back
networks:
django_net:
driver: bridge
volumes:
django_statics:
driver: local
static_volume:
media_volume:
詳細な導線は異なるところはあると思いますが、上記がComposeのコンテナ構成イメージです。
こんな流れで最小限のSPA環境をビルドします。
今回はwebアプリとしてVueからしか繋いでないのに無駄に複雑な構成になってますが、専用コンテナをcomposeに追加してそこからのリクエストをAPIサーバーに処理してもらってNginx経由でアクセスを振り分けてもらえば将来的にネイティブアプリやデスクトップアプリなどクロスプラットフォーム対応した際にもコンテナ同士の構造を意識しなくても良くなる・・・ハズ・・・。
APIサーバー処理用のバックエンド側のコンテナ構成
FROM python:3.8
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
EXPOSE 8000
# 開発ディレクトリ設定
ENV WORKDIR /usr/src/app
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}/
#!/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} | Project initialization -----"
mkdir app && cd app && django-admin startproject config .
sleep 10
# djangoセットアップ&サーバー起動
bash /usr/src/app/scripts/django-settings.sh
bash /usr/src/app/scripts/django-appstart.sh api
# python manage.py createsuperuser
echo "----- ${jst_ymd_today} | Initialization completed -----"
else
echo "----- ${jst_ymd_today} | container update -----"
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
#!/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
#*********************************************************
echo "----- django-settings.sh finish -----"
#*********************************************************
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
django-appstart.sh
」
#!/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
#*********************************************************
echo "----- django-appstart.sh finish -----"
#*********************************************************
初回実行時に「main」ディレクトリを作成するスクリプトですが、他にも新規でアプリディレクトリを追加したい場合は
以下のコマンドでAPPNAMEを好きなアプリ名で実行するとスクリプトが実行され、新規ディレクトリが作成されます
$ docker-compose run back bash /code/scripts/django-appstart.sh APPNAME
app/config/settings.py
」の INSTALLED_APPS
にアプリ名設定追記app/config/urls.py
」の urlpatterns
にアプリルーティング追記app/templates/
」に新規アプリ名ディレクトリを作成urls.py
」に設定追記。(※テスト用のパターン記述は削除)views.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
COPY . ${WORKDIR}/
#!/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
# axiosインストール
npm install axios vue-router
echo "----- Restart the container with the 'docker-compose up' command again -----"
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"
mkdir /code/${app_name}
echo "----- Run the command 'docker-compose run front vue create ${app_name} .' on the host machine -----"
fi
#!/bin/bash
set 'echo -e -o pipefail'
# ルートディレクトリ取得
ROOT_DIR=$(cd $(dirname $0)/..;pwd)
app_name='app'
#***********************************************************************
echo "----- Vue axios設定ファイル作成 -----"
#***********************************************************************
cat > ${ROOT_DIR}/${app_name}/vue.config.js << "EOF"
module.exports = {
devServer: {
proxy: {
'^/api/': {
target: 'http://localhost:8000',
logLevel: 'debug',
pathRewrite: { "^/api/": "/api/" }
}
}
}
}
EOF
#************************************************************************************************
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
すべてのアクセスをさばくプロキシサーバー役のコンテナ構成
FROM nginx:1.11.7
RUN mkdir /var/www
RUN mkdir /var/www/app
RUN mkdir /var/www/static
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_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
┃ ┣━ media/
┃ ┣━ scripts/
┃ ┣━ static/
┃ ┣━ 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
┃ ┣━ static/
┃ ┣━ Dockerfile
┃ ┗━ uwsgi_params
┃
┗━ docker-compose.yml
上手くいけば上記の構成が出来上がってると思います。
http:localhost:8080にアクセスするとVueのトップページが表示されます。
「top」「2」「3」でページが切り替わり、「クリック!」ボタンをクリックするとバックエンドとの通信を行い、現在の時刻を表示します。