Docker ComposeでDjangoとVue.jsの環境構築方法


Docker ComposeでDjango(uwsgi)とMySQL,Nginxの環境構築方法をまとめます。
画面はバックエンドのViewで描画しますが、Vue.jsの環境を用意することで画面の一部をコンポーネントとして描画するようにしています。

今回はindex画面(ロケットが描画されている画面)と管理画面を立ち上げた後、アプリケーションディレクトリを作りアプリケーションのindexページの描画設定を行うところまでまとめています。


# 開発環境
Python:3.7
Django:2.2
Vue.js:2.6.0
MySQL:5.7
Nginx:1.17.0
uWSGI:2.0.18


# 参考
[Docker-Composeで作るDjango開発環境(Django + MySQL + uWSGI + Nginx)](https://nmmmk.hatenablog.com/entry/2018/05/01/101126)



# フォルダ構成

複雑になっていますが、下記のような構成になっています。

```shell-session
project/
    │
    ├──app/
    │   │
    │   ├──src/
    │   │   │
    │   │   ├──myapp/
    │   │   │   │
    │   │   │   ├──__pycache__/
    │   │   │   │
    │   │   │   ├──migrations
    │   │   │   │   │
    │   │   │   │   ├──__pycache__/
    │   │   │   │   ├──__init__.py
    │   │   │   │   └──0001_initial.py
    │   │   │   │
    │   │   │   ├──templates
    │   │   │   │   │
    │   │   │   │   ├──myapp/
    │   │   │   │   │   │
    │   │   │   │   │   └──index.html
    │   │   │   │   │
    │   │   │   │   └──layout.html
    │   │   │   │
    │   │   │   ├──__init__.py
    │   │   │   ├──admin.py
    │   │   │   ├──apps.py
    │   │   │   ├──models.py
    │   │   │   ├──tests.py
    │   │   │   ├──urls.py
    │   │   │   └──views.py
    │   │   │
    │   │   ├──project/
    │   │   │   │
    │   │   │   ├──__init__.py
    │   │   │   ├──settings.py
    │   │   │   ├──urls.py
    │   │   │   └──wsgi.py
    │   │   │
    │   │   ├──static/
    │   │   │   │
    │   │   │   ├──css/
    │   │   │   │   │
    │   │   │   │   └──app.css
    │   │   │   │
    │   │   │   ├──img/
    │   │   │   │
    │   │   │   └──js/
    │   │   │       │
    │   │   │       ├──components/
    │   │   │       └──app.js
    │   │   │
    │   │   ├──manage.py
    │   │   ├──package-lock.json
    │   │   ├──package.json
    │   │   └──webpack.config.js
    │   │
    │   ├──static/
    │   │   │
    │   │   ├──admin/   *管理対象外
    │   │   └──bundles/ *管理対象外
    │   │
    │   ├──Dockerfile
    │   └──requirements.txt
    │
    ├──mysql/
    │   │
    │   ├──mysql_data/   *管理対象外
    │   │
    │   ├──sql/
    │   │   │
    │   │   └──initialize.sql
    │   │
    │   ├──Dockerfile
    │   └──my.cnf
    │
    ├──nginx/
    │   │
    │   ├──Dockerfile
    │   ├──nginx.cnf
    │   └──iwsgi_params
    │
    ├──.editorconfig
    ├──.env_example
    ├──.gitignore
    ├──docker-compose.yml
    └──Readme.md
```

# 環境構築


## docker-comopose.ymlの内容

.envでポート番号等の設定を行なっています。
DockerファイルやDB作成用のSQLは参考のものを流用しています。
* uwsgiコンテナにはnpmをインストールする様に編集しています。

```yml

version: "3.5"

services:

  uwsgi:
    build: ./app
    container_name: project_uwsgi
    volumes:
      - ./app:/var/www/
    expose:
      - "${UWSGI_PORT}"
    depends_on:
      - db
    ports:
      - "${UWSGI_PORT}:${UWSGI_PORT}"
    tty: true
    environment:
      TZ: "Asia/Tokyo"
      APP_URL: ${APP_URL}
      APP_ENV: ${APP_ENV}
      DB_USER: ${DB_USER}
      DB_PASSWORD: ${DB_PASSWORD}
      DB_HOST: ${DB_HOST}
      DB_DATABASE: ${DB_DATABASE}

  nginx:
    build: ./nginx
    container_name: project_nginx
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
      - ./app/static:/static
    depends_on:
      - uwsgi
    ports:
      - "${NGINX_HOST_HTTP_PORT}:${NGINX_HOST_HTTP_PORT}"
      - "${NGINX_HOST_HTTPS_PORT}:${NGINX_HOST_HTTPS_PORT}"
    environment:
      TZ: "Asia/Tokyo"

  db:
    build: ./mysql
    container_name: project_db
    volumes:
      - ./mysql/mysql_data:/var/lib/mysql # データの永続化
      - ./mysql/sql:/docker-entrypoint-initdb.d
    ports:
      - "${MYSQL_PORT}:${MYSQL_PORT}"
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      TZ: "Asia/Tokyo"

```

## requirements.txtの内容

```text
Django==2.1.1
uwsgi==2.0.18
mysqlclient==1.4.6
django-webpack-loader==0.6.0
Pillow==6.2.1
```

## 

## 実際の環境構築手順

### docker-composeコマンドでstartprojectの実行

```shell-session
$ docker-compose run uwsgi django-admin.py startproject project .
```

### settings.pyに下記の事項をそれぞれ所定の箇所に記載する

DB設定

```Python
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db_name',
        'USER': 'user_name',
        'PASSWORD': 'user_pass',
        'HOST': 'db_port',
        'PORT': '3306',
        'OPTIONS': {
            'charset': 'utf8mb4'
        },
    }
}
```

文字コード、タイムゾーン

```Python
LANGUAGE_CODE = 'ja_JP.UTF-8'

TIME_ZONE = 'Asia/Tokyo'
```

collectstaticコマンド実行時に静的ファイルを集める場所を指定

```Python
STATIC_ROOT = '/var/www/static'
```

トップページに表示する画像などを格納するパスの設定「/static」
webpackでビルドしたファイルの格納場所の指定

```Python
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

WEBPACK_LOADER = {
    'DEFAULT': {
        'BUNDLE_DIR_NAME': 'bundles/',
        'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
    }
}
```

### マイグレーションの実行

```shell-session
$ docker-compose run uwsgi ./manage.py makemigrations
$ docker-compose run uwsgi ./manage.py migrate
```

### 管理画面作成

管理者名、メールアドレス、パスワードを作成する

```shell-session
$ docker-compose run uwsgi ./manage.py createsuperuser
```

任意の値を設定する。

### 管理者画面のCSS設定

settings.pyに「STATIC_ROOT」の記述をした状態で下記を実行する

```shell-session
$ docker-compose run uwsgi ./manage.py collectstatic
```

### npm環境の構築

(uwsgiコンテナにて)Dockerファイルで特にnpmの設定をしていない場合、下記の手順でnpmをインストールします。


```shell-session
apt install -y nodejs npm
npm install -g n
n stable
apt purge -y nodejs npm
```


### Vue.jsのインストール

webpackなどもこのタイミングでインストールしておきます。

```shell-session
npm install --save-dev webpack webpack-cli webpack-bundle-tracker
npm install --save-dev vue-loader
npm install --save-dev style-loader css-loader sass-loader
npm install --save vue
npm install --save-dev @babel/core @babel/preset-env
npm install --save-dev babel-loader
```

### npm installの実行

下記のようなpackage.jsonを予め用意しておけばnpm installコマンドだけでインストール出来ます。

```shell-session
$ docker-compose run uwsgi npm install
```

### package.jsonの内容

場所:app/src/package.json

```json
{
  "name": "app",
  "version": "1.0.0",
  "description": "### myapp application.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "node_modules/.bin/webpack --mode development",
    "build": "node_modules/.bin/webpack --mode production",
    "watch": "node_modules/.bin/webpack --watch -d"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.7.4",
    "@babel/preset-env": "^7.7.4",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.2.1",
    "sass-loader": "^8.0.0",
    "style-loader": "^1.0.1",
    "vue-loader": "^15.7.2",
    "vue-template-compiler": "^2.6.10",
    "webpack": "^4.41.2",
    "webpack-bundle-tracker": "^0.4.3",
    "webpack-cli": "^3.3.10"
  },
  "dependencies": {
    "vue": "^2.6.10"
  }
}
```

### webpack.config.jsの作成

```js
var path = require("path");
var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');
var VueLoaderPlugin = require('vue-loader/lib/plugin');

var env = process.env.NODE_ENV;


module.exports = {
    context: __dirname,
    entry: './static/js/app.js',

    output: {
        filename: "[name]-[hash].js",
        path: path.resolve('../static/bundles/'),
        // publicPath: '/'
    },

    plugins: [
        new BundleTracker({filename: './webpack-stats.json'}),
        // .vueファイルを読み込めるようにする
        new VueLoaderPlugin()
    ],

    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            {
                test: /\.scss$/,
                use: [
                    'style-loader',
                    'css-loader',
                    'sass-loader'
                ]
            },
            {
                test: /\.css$/,
                use: [
                    "vue-style-loader",
                    'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            url: false,
                            sourceMap: true
                        }
                    }
                ]
            },
        ]
    },

    resolve: {
        extensions: ['.js', '.vue'],
        modules: [
            "node_modules"
        ],
        alias: {
            'vue': path.resolve('./node_modules/vue/dist/vue.js'),
        }
    },
}
```


### アセットコンパイル

```shell-session
$ docker-compose run uwsgi npm run dev
```

### コンテナの起動

```shell-session
$ docker-compose up -d
```

### ブラウザでチェック

```
http://localhost
```

これでindexの画面(ロケットが表示されている画面)と管理画面が見れるようになります！

## アプリケーションの新規作成

```shell-session
$ docker-compose run uwsgi ./manage.py startapp myapp
```

project/settings.pyの「INSTALLED_APPS」に作成したアプリケーション名を追記すること。

```Python
INSTALLED_APPS = {
...
    'myapp',
...
}
```

sttings.pyの「INSTALLED_APPS」に下記を追記

```Python
INSTALLED_APPS = {
...
    'webpack_loader',
    'myapp',
...
}
```

アセットコンパイル

```shell-session
$ docker-compose run uwsgi npm run dev
```

myappアプリケーションのページをブラウザで表示する為の設定は下記の通りです。

### myapp/urls.pyの作成

```
touch app/src/myapp/urls.py
```

下記の通り編集

```Python
from django.urls import path
from . import views

app_name = 'myapp'
urlpatterns = [
    path('', views.index, name='index'),
]
```

### myapp/views.pyの編集

```Python
from django.http import HttpResponse
from django.template import loader

def index(request):
    template = loader.get_template('myapp/index.html')
    context = {}
    return HttpResponse(template.render(context, request))
```

### project/urls.pyの編集

```Python
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls')),
]
```

あとは「/app/src/myapp/templates/myapp」ディレクトリにindex.htmlを作成することでmyappのindexページ(URL:localhost)をブラウザから開くことが出来るようになります。
jsファイルやcssファイルを追加して編集した際は随時npm runでビルドをします。



## 補足:不要ファイルの削除

環境を再構築したい時は下記のコマンドで既存のものを削除します。


```shell-session
$ docker-compose down --rmi all
$ unlink app/src/myapp
$ rm -rf app/src/node_modules
$ rm -rf app/src/project
$ rm -rf app/src/manage.py
$ rm -rf app/static/*
$ rm -rf mysql/mysql_data
```

# まとめ
Laravelだと比較的直ぐにアセットのビルド環境を構築出来ていたのですが、Djangoの場合だとWebpackの設定ファイルを作るところから始めたのが新鮮で楽しかったです！
上記の内容でならアプリケーションコンテナでnpm run buildを実行することでVue.jsの画面を作成することが出来ますが、やはりフロントの環境をPythonのアプリケーションに混ぜると煩雑になり管理しにくくなります。
今回まとめた方法はあくまでViewをサーバーサイドで持ちたいと言う場合に使えそうな手段ですが、今後環境を構築するのならフロントエンドとバックエンドに分けて構築しようと思いました。