はじめに
以前、こんな記事を書きました。
Docker で Django の開発環境を構築!(Docker-compose/Django/postgreSQL/nginx)
元記事はだいぶ昔(1年くらい?)に書いたものですが、今では知見が溜まりもうちょっと良い感じの構成に出来るんじゃないかなーと思ってチャレンジしてみることにしました!
Dockerのインストールなどの細かい話はしないので、気になる方は元記事を見てみてくださいね!
(こちらなんかが参考になると思います。)
また、以前の記事と明確に異なる点として、DjangoはAPI配信サーバーとして考え、フロントエンドの実装にはVue.jsやReactを想定しているので、静的ファイルの配信やtemplateの設定などは行わない点と、docker-compose.yml
とdocker-compose.yml.prod
を作って本番環境と開発環境を分ける…みたいなことはやっていないのでご了承ください。
目次
- 目次
- ディレクトリ構成
- Django環境の構築
- PostgreSQL環境の構築
- docker-composeを利用してみる
- nginx環境の構築
- docker-compose.ymlにnginxを追加する
ディレクトリ構成
最終的に、こんな感じのディレクトリ構成になりました!
backend
└── containers
├── django
│ ├── Dockerfile
│ ├── Pipfile
│ ├── Pipfile.lock
│ ├── config
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── entrypoint.sh
│ ├── manage.py
├── docker-compose.yml
├── nginx
│ ├── Dockerfile
│ └── nginx.conf
└── postgres
├── Dockerfile
└── sql
└── init.sql
backendディレクトリ以下のcontainersディレクトリに各コンテナを配置しています。
前回の記事と比較してpostgresディレクトリが追加されたりしています。
では早速、一番上のdjangoディレクトリから構成していきましょう!
Django環境の構築
まずbackendとcontainersとdjangoの3つのディレクトリを作成していきましょう!そしてdjangoディレクトリに移動します。
$mkdir -p backend/containers/django
$cd backend/containers/django
まずはdjangoディレクトリ内にPipfileを作成しましょう。各パッケージのバージョンは本記事執筆時点最新版であり、特にこだわりはありません。ご自由に変更ください。(動作は保証していません)
また、インストールするパッケージは任意であり環境構築のみが目的であれば、django
とgunicorn
とdjango-environ
のみが入っていれば問題ありません。
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
django = "==3.0.6"
djangorestframework = "==3.11.0"
djangorestframework-simplejwt = "==4.4.0"
djangorestframework-gis = "==0.15"
django-cors-headers = "==3.2.1"
django-environ = "==0.4.5"
djoser = "==2.0.3"
gunicorn = "==20.0.4"
psycopg2-binary = "==2.8.5"
[requires]
python_version = "3.8.2"
pipenvで仮想環境をpython3.8系で作成していき、仮想環境に入ってバージョンとインストール済みのパッケージを確認していきましょう。
$pipenv install
$pipenv shell
(django) $python -V
Python 3.8.2
(django) $pip list
Package Version
----------------------------- -------
asgiref 3.2.7
Django 3.0.6
django-cors-headers 3.2.1
django-environ 0.4.5
django-templated-mail 1.1.1
djangorestframework 3.11.0
djangorestframework-gis 0.15
djangorestframework-simplejwt 4.4.0
djoser 2.0.3
gunicorn 20.0.4
pip 20.0.2
psycopg2-binary 2.8.5
PyJWT 1.7.1
pytz 2020.1
setuptools 46.1.3
sqlparse 0.3.1
wheel 0.34.2
シェルの左端に(django)
の文字が表示されていれば仮想環境に入れています。(簡略化のため次回以降は(django)
の文字列を記載しません)
ちゃんとDjangoがインストールされていますね!
djangoディレクトリにいるのを確認したら、Djangoのコマンドを利用してプロジェクトを作成します。
$django-admin startproject config .
これでDjangoプロジェクトの作成完了です。configディレクトリとmanage.py
という名前のファイルがdjangoディレクトリに作成されているのが確認できるはずです。
この時点でpython manage.py runserver localhost:8000
コマンドでデバッグサーバーを起動し、ブラウザからloaclhost:8000
に接続すると親の顔よりも見たあの画面が表示されているはずです。
お次にDjango周りのファイルを以下のように修正していきましょう。
SECRET_KEYについてはあとで利用するのでコメントアウトするなどしておいてください。
import os
from datetime import timedelta
import environ
env = environ.Env()
env.read_env('.env')
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = env.get_value('SECRET_KEY')
DEBUG = env.get_value('DEBUG')
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_gis',
'corsheaders',
'django.contrib.gis',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'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': [os.path.join(BASE_DIR, 'templates')],
'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'
DATABASES = {
'default': {
'ENGINE': env.get_value('DATABASE_ENGINE', default='django.db.backends.sqlite3'),
'NAME': env.get_value('DATABASE_DB', default=os.path.join(BASE_DIR, 'db.sqlite3')),
'USER': env.get_value('DATABASE_USER', default='django_user'),
'PASSWORD': env.get_value('DATABASE_PASSWORD', default='password'),
'HOST': env.get_value('DATABASE_HOST', default='localhost'),
'PORT': env.get_value('DATABASE_PORT', default='5432'),
}
}
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',
},
]
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_URL = '/static/'
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
SIMPLE_JWT = {
'AUTH_HEADER_TYPES': ('JWT',),
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
}
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = (
'http://localhost:8080',
'http://127.0.0.1:8080',
)
最初にPipfileを作成したときにdjango-environ
というパッケージを追加しましたが、これはSECRET_KEYやDBへの接続情報など、隠しておきたい情報を.env
ファイルなどに記載し、ソースコードとは別に管理するためのパッケージで、setting.py
などから
env = environ.Env()
env.read_env('.env')
のように利用します。
早速.env
ファイルを作成していきましょう。
DEBUG=True
SECRET_KEY=<YOUR_SECRET_KEY>
DATABASE_ENGINE=django.contrib.gis.db.backends.postgis
DATABASE_DB=<YOUR_DB_NAME>
DATABASE_USER=<YOUR_DB_USER>
DATABASE_PASSWORD=<YOUR_DB_PASSWORD>
#entrypoint.shで利用
#compose.ymlに記載のサービス名で名前解決してくれる
DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE=postgres
- :setting.pyに元々記載されていたKEY
- (USER/PASSWORD):任意のもの入れてください。詳しくはPostgreSQLコンテナを立ち上げる際に説明します
あとはゴリゴリと関連するファイルを直していきます。backendディレクトリからのパスも記載するので、適宜必要なファイルの作成と追記・修正を行っていきます。
# ubuntuのイメージをプルし、Pythonをインストールしていく
FROM ubuntu:20.04
SHELL ["/bin/bash", "-c"]
# pythonをインストール
RUN apt-get update -y \
&& apt-get upgrade -y \
&& apt-get install -y python3.8 python3.8-dev \
&& source ~/.bashrc \
&& apt-get -y install vim
# 作業ディレクトリを設定
WORKDIR /usr/src/app
# 環境変数を設定
# Pythonがpyc filesとdiscへ書き込むことを防ぐ
ENV PYTHONDONTWRITEBYTECODE 1
# Pythonが標準入出力をバッファリングすることを防ぐ
ENV PYTHONUNBUFFERED 1
ENV DEBIAN_FRONTEND=noninteractive
# 依存関係のインストールとpipenvをインストール
RUN apt-get install -y curl \
&& curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py \
&& apt-get install -y python3.8-distutils \
&& python3.8 get-pip.py \
&& pip install -U pip \
&& apt-get install -y build-essential libssl-dev libffi-dev python-dev python3-dev libpq-dev
# pipenvのインストール
RUN pip install pipenv
# ローカルマシンののPipfileをコンテナにコピー
COPY Pipfile ./
# Pipfile.lockを無視してPipfileに記載のパッケージをシステムにインストール
# その後、pipenvをアンインストール
RUN pipenv install --system --skip-lock \
&& pip uninstall -y pipenv virtualenv-clone virtualenv
# 地理空間ライブラリをインストールする際の依存関係
RUN apt-get update -y \
&& apt-get upgrade -y \
&& apt-get install -y libgeos-dev binutils libproj-dev gdal-bin libgdal-dev \
&& apt-get install -y python3-gdal
RUN apt-get install -y netcat \
&& apt-get install -y expect
# シェルスクリプトをコピー
# COPY ./entrypoint.sh /usr/src/app/entrypoint.sh
COPY . /usr/src/app/
# シェルスクリプトを実行
# ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
この段階でコンテナが動作するかどうか確認してみましょう。
$docker build . -t pipenv_sample
...
$docker run -it pipenv_sample
root@e6bdfb335bee:/usr/src/app#
このようにコンテナにrootユーザーでログインできれば、おそらく成功です!
# python3 -V
Python 3.8.2
# pip list
Package Version
----------------------------- ----------
appdirs 1.4.3
asgiref 3.2.7
certifi 2020.4.5.1
distlib 0.3.0
Django 3.0.6
django-cors-headers 3.2.1
django-environ 0.4.5
django-templated-mail 1.1.1
djangorestframework 3.11.0
djangorestframework-gis 0.15
djangorestframework-simplejwt 4.4.0
djoser 2.0.3
filelock 3.0.12
GDAL 3.0.4
gunicorn 20.0.4
numpy 1.17.4
pip 20.1
pipenv 2018.11.26
psycopg2-binary 2.8.5
PyJWT 1.7.1
pytz 2020.1
setuptools 46.1.3
six 1.14.0
sqlparse 0.3.1
virtualenv 20.0.20
virtualenv-clone 0.5.4
wheel 0.34.2
Pythonのバージョンは3.8.2になっていますし、パッケージがちゃんとインストールされていますね!
Control + d
でログアウトしましょう。
最後に、起動時に実行するpostgresコンテナとの接続用shellスクリプトを書いて保存しましょう。
#!/bin/sh
if [ "$DATABASE" = "postgres" ]
then
echo "Waiting for postgres..."
while ! nc -z $DATABASE_HOST $DATABASE_PORT; do
sleep 0.1
done
echo "PostgreSQL started"
fi
exec "$@"
ついでに、上で編集した Dockerfile の最終行付近にある 2 つのコメントアウトを外してください。
# これ
COPY ./entrypoint.sh /usr/src/app/entrypoint.sh
COPY . /usr/src/app/
# これ
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
ひとまず、Djangoディレクトリについては以上です。
- ディレクトリ構成
backend
└── containers
├── django
│ ├── Dockerfile
│ ├── Pipfile
│ ├── Pipfile.lock
│ ├── config
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── entrypoint.sh
│ ├── manage.py
└── docker-compose.yml
PostgreSQL環境の構築
次はPostgreSQLコンテナを立ち上げていきます。
まずは、containersディレクトリに移動してpostgresディレクトリなどを作成しましょう。
$cd ../../
$mkdir -p postgres/sql/
これで、postgresディレクトリとその中にsqlディレクトリができたと思います。
postgresディレクトリの中にDockerfileを追加していきましょう。
FROM mdillon/postgis:11
RUN localedef -i ja_JP -c -f UTF-8 -A /usr/share/locale/locale.alias ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
僕の場合、作成したいのは地図アプリケーションだったので、PostgreSQLの拡張機能であるPostGISのイメージを利用していきますが、任意のPostgreSQLイメージで構わないと思います。(動作は検証はしていません)
次に、sqlディレクトリ内にコンテナ起動時に実行したいsqlファイルを格納しましょう。
CREATE EXTENSION postgis;
今回は拡張機能を有効にするためのsqlファイルだけ格納しましたが、初期登録したいデータなどがあればそれをご自由に格納してください。
最後に、.env_dbファイルを追加していきます。
この中には、Djangoコンテナを作成したときに記載した、(USER/PASSWORD)と同じものを書いてください。
ここに記載した内容で、DBが自動作成されます。
#envに書いておけば自動でDBが作成される
POSTGRES_DB=<YOUR_DB_NAME>
POSTGRES_USER=<YOUR_DB_USER>
POSTGRES_PASSWORD=<YOUR_DB_PASSWORD>
以上で、postgres環境の構築終了です。
- ディレクトリ構成
backend
└── containers
├── django
│ ├── Dockerfile
│ ├── Pipfile
│ ├── Pipfile.lock
│ ├── config
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── entrypoint.sh
│ ├── manage.py
├── docker-compose.yml
└── postgres
├── Dockerfile
└── sql
└── init.sql
docker-composeを利用してみる
docker-composeは複数のコンテナを同時起動させたり、それらを接続する際に利用する便利ツールです。
早速、containersディレクトリに移動し、設定ファイルを作っていきましょう。
$cd ../
$touch docker-compose.yml
version: "3.7"
services:
django:
# コンテナ名
container_name: django
# ビルドするdockerファイルが格納されたディレクトリ
build: ./django
# 正常起動後に実行するコマンド
command: python3 manage.py runserver 0.0.0.0:8000
volumes:
# マウントするディレクトリ
- ./django:/usr/src/app/
ports:
# ホスト側のポート:コンテナ側のポート
- 8000:8000
env_file:
# 環境変数に設定するファイル
- ./django/.env
depends_on:
# 接続するサービス
- postgres
postgres:
container_name: postgres
build: ./postgres
volumes:
# DBのデータはボリュームを作成して保存
# ディレクトリとマウントとって実データをホストOSに直接残しても良い
# /var/lib/postgresql/dataにDBのデータが格納されている
- sample_postgis_data:/var/lib/postgresql/data
# down -vなどでボリュームがない時などを含めた初回起動時に実行されるファイルを指定
- ./postgres/sql:/docker-entrypoint-initdb.d
env_file: ./postgres/.env_db
ports:
# ホスト側のポートはローカルのpsqlとバッティングするので5432以外にする
- 5433:5432
volumes:
sample_postgis_data:
書きおわっったらdocker-composeを立ち上げてみましょう。
$docker-compose up -d --build
localhost:8000
にブラウザから接続してみましょう。
兄の顔より見たDjangoの初期画面が表示されていれば成功です。
コンテナには以下のコマンドでログインできます。
$docker exec -it <サービス名> bash
つまり今回であれば以下で接続できます。
$docker exec -it django bash
または
$docker exec -it postgres bash
djangoコンテナの起動は確認しましたので、postgresコンテナも確認しましょう。
$docker exec -it postgres bash
#psql -U <YOUR_DB_USER> -d <YOUR_DB_NAME>
psql (11.2 (Debian 11.2-1.pgdg90+1))
"help" でヘルプを表示します。
<YOUR_DB_NAME>=#
<YOUR_DB_NAME>=# SELECT postgis_version();
postgis_version
---------------------------------------
2.5 USE_GEOS=1 USE_PROJ=1 USE_STATS=1
(1 行)
DBが指定したもので作成されており、postgisも有効になっているのが確認できました。
以下のコマンドで一旦コンテナを停止・イメージを削除しましょう。
$docker-compose down -v
nginx環境の構築
最後にnginxの環境を構築していきましょう。
まずはnginxディレクトリを作成してDockerfileも作成していきます。
$mkdir nginx
$cd nginx/
$touch Dockerfile
FROM nginx:1.17.10
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d
次に
upstream config {
# コンテナのサービス名を指定すると名前解決してくれる
server django:8000;
}
server {
# 80ポートで待ち受け
listen 80;
location / {
proxy_pass http://config;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
# 静的ファイルの要求をstaticにルーティング
location /static/ {
alias /usr/src/app/static/;
}
}
一通り、必要なファイルは揃った思います。
docker-compose.ymlにnginxを追加する
以下のようにnginxサービスを追加し、djangoサービスのcommand
をrunserver
からgunicorn
に変更してください。
version: "3.7"
services:
django:
# コンテナ名
container_name: django
# ビルドするdockerファイルが格納されたディレクトリ
build: ./django
# 正常起動後に実行するコマンド
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
volumes:
# マウントするディレクトリ
- ./django:/usr/src/app/
ports:
# ホスト側のポート:コンテナ側のポート
- 8000:8000
env_file:
# 環境変数に設定するファイル
- ./django/.env
depends_on:
# 接続するサービス
- postgres
postgres:
container_name: postgres
build: ./postgres
volumes:
# DBのデータはボリュームを作成して保存
# ディレクトリとマウントとって実データをホストOSに直接残しても良い
# /var/lib/postgresql/dataにDBのデータが格納されている
- sample_postgis_data:/var/lib/postgresql/data
# down -vなどでボリュームがない時などを含めた初回起動時に実行されるファイルを指定
- ./postgres/sql:/docker-entrypoint-initdb.d
env_file: ./postgres/.env_db
ports:
# ホスト側のポートはローカルのpsqlとバッティングするので5432以外にする
- 5433:5432
nginx:
container_name: nginx
build: ./nginx
volumes:
- ./django/static:/usr/src/app/static
ports:
- 80:80
depends_on:
- django
volumes:
sample_postgis_data:
コンテナを起動させてみます。
$docker-compose up -d --build
nginxコンテナで80ポートから配信できているのが確認できれば環境構築完了です。
localhost
に接続してみましょう。
姉の顔より見たあの画面が表示されていますね。
お疲れ様でした。あとは煮るなり焼くなりお好きにどうぞ!
- 最終的なディレクトリ構成(冒頭に記載のものと同じ)
backend
└── containers
├── django
│ ├── Dockerfile
│ ├── Pipfile
│ ├── Pipfile.lock
│ ├── config
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── entrypoint.sh
│ ├── manage.py
├── docker-compose.yml
├── nginx
│ ├── Dockerfile
│ └── nginx.conf
└── postgres
├── Dockerfile
└── sql
└── init.sql