Help us understand the problem. What is going on with this article?

DjangoをDocker Composeでupしよう!

今更ながらDockerの勉強を始めました。
とりあえずプログラマのためのDocker教科書 第2版を読み終えたので力試しのトライです。
今回はnginx + (Gunicorn + Django) + PostgreSQLな構成で作成します。
本記事で使用しているコードはここにあるので気になった方は見てみてください。

開発環境

  • macOS High Sierra 10.13.6
  • Docker for Mac
    • Engine : 18.06.1-ce
    • Compose : 1.22.0
  • Python : 3.7
  • Django : 2.1

0.プロジェクトの雛形用意

今回のディレクトリの構成このようになっています。

docker-django
├── docker-compose.yml
├── django
│   ├── Dockerfile
│   ├── Dockerfile.base
│   ├── requirements.txt
│   └── requirements.base.txt
└── nginx
    ├── mime.types
    └── nginx.conf

コンテナ毎の設定ファイルをそれぞれのコンテナ名のディレクトリ下に配置しています。
例えばnginx用のコンテナに使用する設定ファイルはnginx/のフォルダに格納されています。

1.Django+Gunicorn用のベースイメージを作成

はじめにDjangoプロジェクトをゴリゴリ開発するためのベースイメージを作成します。
このイメージの用途はdjangoのプロジェクト作成コマンド(django-admin startproject)を実行するときのコンテナに使ったり、後に作成するデプロイ用イメージのベースイメージとして使用します。

ベースイメージのDockerfileはこのようになります。

Dockerfile.base
FROM python:3.7

ENV APP_PATH /opt/apps

COPY requirements.base.txt $APP_PATH/
RUN pip install --no-cache-dir -r $APP_PATH/requirements.base.txt

WORKDIR $APP_PATH

また、requirements.base.txtの内容はこのようになっています。

requirements.base.txt
Django==2.1.11
gunicorn==19.9.0
psycopg2==2.7.5
psycopg2-binary==2.7.5

これをサクッとビルドしましょう。

$ docker build -t django2.1 -f ./django/Dockerfile.base ./django
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
django2.1           latest              c391e4c70e98        6 minutes ago       960MB
python              3.7                 825141134528        4 weeks ago         923MB

python:3.7のイメージをもとにdjango2.1:latestというイメージが作成されたのがわかります。

2.Djangoプロジェクトのローカルへの作成

Djangoを使用できるイメージを作成したところで、
次はこのイメージを使ったコンテナからプロジェクトファイルを作成します。
ここでキモとなるのがコンテナ作成時にdocker-django/django/をmountオプションでバインドします。
こうすることで何が嬉しいかというとコンテナがあたかもvirtualenvのように扱えるのです!
早速次のコマンドを実行します。

$ cd django
$ docker run --rm \
--mount type=bind,src=$(pwd),dst=/opt/apps \
django2.1 \
django-admin startproject my_docker_project .

少しコマンドが長くなってしまいましたので改行をいれてあります。
一行ずつみていきましょう。
まずはDjangoプロジェクトを配置したい、./django/へカレントディレクトリを移動します。
次の行からコンテナの起動オプションです。
1行目の--rmはコマンドの実行が終えたらコンテナを削除するオプションです。
2行目の--mountはコンテナ起動時にマウントするホストディレクトリなどを設定します。今回はホストへの読み書きを許可するのでtypeはbind,srcはカレントディレクトリ,dstはコンテナ側のカレントディレクトリを指定します。
3行目にはコンテナのもととなるイメージを指定しています。
4行目がコンテナ内で実行されるコマンドです。今回はmy_docker_projectという名前のプロジェクトを作成しました。

実行後、djangoフォルダの中身をみてみると、

$ tree
.
├── Dockerfile
├── Dockerfile.base
├── manage.py
├── my_docker_project
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── requirements.base.txt
└── requirements.txt

Djangoのプロジェクトファイルがローカルに作成されています。素晴らしい!!
このようにDjangoのコマンドを使ってあれこれするときは、ホストをマウントしたコンテナで実行していきます。

ここまでできたらDjango関連は一旦おやすみです。

3.docker-compose.ymlの作成と各コンテナ間の接続設定

Django+Gunicornな環境構築が落ち着いたところで、視点をコンテナの全体構成に向けます。
目標は、nginx + (Gunicorn + Django) + PostgreSQLな構成でした。
docker-compose.ymlは以下のようになります。

docker-compose.yml
version: '3.7'
services:
  django:
    restart: always
    build: ./django
    expose:
      - "3031"
    depends_on:
      - postgres
    command: bash -c "python manage.py migrate && gunicorn my_docker_project.wsgi -b 0.0.0.0:3031"
    volumes:
      - "staticdata:/opt/static/"
  nginx:
    restart: always
    image: nginx
    depends_on:
      - django
    ports:
      - "80:80"
    volumes:
      - "./nginx/:/etc/nginx/"
      - "staticdata:/opt/apps/static/"
  postgres:
    image: postgres
    ports:
      - "5432:5432"
    volumes:
      - "dbdata:/var/lib/postgresql/data"
    environment:
      POSTGRES_PASSWORD: hogemojahogemoja

volumes:
  dbdata:
  staticdata:

ここでは、
「DBのデータは永続化したい。」
「静的ファイルはnginxコンテナから配信したい。」
がキモになります。

「DBのデータは永続化したい。」 <=> 「コンテナ外部のボリュームを使いたい」

ので、docker-compose.ymlのトップレベルに dbdataというボリュームを宣言し、
それをコンテナにマウントしています。

「静的ファイルはnginxコンテナから配信したい。」 <=> 「コンテナ間でリソースを共有したい。」

ので、同じくトップレベルにstaticdataというボリュームを宣言し、
それをdjangoとnginx両方のコンテナにマウントしています。

3-1.nginxコンテナの設定

今回の構成ではnginxで受け取ったリクエストを適宜Django(Gunicorn)に振り分けたり、静的ファイルを配信したりします。
さっそくconfファイルに書いていきましょう。

nginx.conf
http {
#~中略~
  include /etc/nginx/mime.types;
#~中略~
  upstream django_server {
    #docker-compose.ymlに記入したサーバ名、ポートを指定します。 #
    server django:3031 fail_timeout=0;
  }

  server {
#~中略~
    location /static/ {
            #先ほどしていた静的ファイルのボリュームのマウント先を指定します。#
      alias /opt/apps/static/;
    }

    location / {
      try_files $uri @proxy_to_django;
    }

    location @proxy_to_django {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_redirect off;
      proxy_pass http://django_server;
    }
#~中略~
  }
}

nginxコンテナの設定は以上です。
今回は最小の構成で、mime.typesはデフォルトのまま使用しています。

3-2.djangoコンテナの設定とビルド用のDockerfile作成

docker-compose.ymlやnginx.confファイルの情報をもとにsettings.pyを編集していきます。

settings.py
#〜中略〜
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',  #作成時のデフォルト
        'USER': 'postgres',  #作成時のデフォルト
        'PASSWORD' : 'hogemojahogemoja', #作成時にdocker-compose.ymlで設定
        'HOST' : 'postgres', #コンテナのサーバ名
        'PORT' : 5432,
    }
}
#〜中略〜
STATIC_URL = '/static/'
STATIC_ROOT = '/opt/static' #ボリュームマウント先のパス
#〜中略〜

続いてDockerfileを編集していきます。

Dockerfile
FROM django2.1:latest

ENV APP_PATH /opt/apps

COPY . $APP_PATH/
RUN pip install --no-cache-dir -r $APP_PATH/requirements.txt

WORKDIR $APP_PATH
RUN python manage.py collectstatic --noinput

こちらのイメージは先ほどのベースイメージとは異なり、デプロイ用のイメージです。
プロジェクトで使用しているpythonライブラリをインストールしたり、配信用に静的ファイルをまとめたりしてます。

4.Docker Composeでupする。

お疲れさまでした。
あとはupするだけです。

$ docker-compose up --build

各コンテナが立ち上がったあとlocalhostにアクセスすると…
スクリーンショット 2018-09-04 20.08.47.png
素晴らしい!!

スクリーンショット 2018-09-04 20.09.01.png
CSSもちゃんと効いてます!

5.終わりに

Dockerを触り始めて日が浅いですが
こんな風に各コンテナが協調して動いてるのが作れると脳汁出まくりでホント楽しいです。
今後もっと業務や趣味にDocker取り入れて行きたいです!!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away