Docker 入門
Python Django サーバを Docker で構築する
SQLite を使った Django プロジェクトを公開する
このセクション,Python Django のプロジェクトを Docker を使って Web に公開する方法をいくつか紹介します.まずこのページではデータベースに SQLite を用いた最も単純な環境を構築します.具体的には,Nginx コンテナを Web サーバとして http://localhost/ という URL でリクエストを受け付け,Python Django で開発したコメントアプリケーションサーバのコンテナに転送します.SQLite データベースはアプリケーションサーバ内で管理することにします.
なお,このページの Docker サンプルコードは GitHub の Django01sqlite フォルダで公開しています.また,Python Django のコードも GitHub で公開しています.
任意の新規ディレクトリを作成し,その中に次の構成でディレクトリとファイルを設置します.なお,Django のプロジェクトは django ディレクトリに設置しますが,Docker の Git リポジトリに Django のコードが含まれないように,django/.gitignore を設置しておきます.また,ログファイルも Git に含まれないように logs/nginx/.gitignore を設置しておきます.
% tree -a -F ⏎
./
├── .gitignore
├── django/
│ └── .gitignore
├── docker-compose.yml
├── logs/
│ └── nginx/
│ └── .gitignore
├── nginx/
│ ├── conf.d/
│ │ └── default.conf
│ └── Dockerfile
├── python/
│ ├── Dockerfile
│ └── entrypoint.sh*
└── Readme.md
7 directories, 9 files
全体の設計図である docker-compose.yml は次のような内容にします.Python のアプリケーションサーバである python_app と Web サーバである web という2つのサービスで全体を構成します.
docker-compose.yml
services:
python_app:
build:
context: .
dockerfile: ./python/Dockerfile
container_name: python_app_01
volumes:
- ./python/entrypoint.sh:/entrypoint.sh
- ./django/code:/code
expose:
- "8000"
command: /bin/sh -c "pip install -r /code/requirements.txt && chmod +x /entrypoint.sh && /entrypoint.sh"
web:
image: nginx:1.25.5
container_name: python_nginx_01
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
# - ./web/html:/usr/share/nginx/html
- ./django/code/comments/static:/static
- ./logs/nginx:/var/log/nginx
ports:
- "80:80"
depends_on:
- python_app
docker-compose.yml の5行目で,Dockerfile のパスを指定しています.Dockerfile には次の内容を入力します.
python/Dockerfile
FROM python:3.11
# pycファイル(および__pycache__)の生成を行わないようにする
ENV PYTHONDONTWRITEBYTECODE=1
# 標準出力・標準エラーのストリームのバッファリングを行わない
ENV PYTHONUNBUFFERED=1
# コンテナのワークディレクトリを/codeに指定
WORKDIR /code
RUN pip install --upgrade pip
python/entrypoint.sh は次の内容を入力します.このスクリプトはコンテナ起動時に実行されます.2行目の pip list は余分なコードですが,これを入れておくことでコンテナ出力にインストール済みのパッケージが表示されることから,慣れないうちはスクリプトの実行状況が確認しやすくなります.また,3行目は gunicorn という HTTP サーバを起動する命令です.このコードは docker-compose.yml の5行目にある command によって実行されます.つまり,コンテナが起動した直後に Django プロジェクトの requirements.txt に記載されたパッケージがインストールされたあと,entrypoint.sh が実行されるという順番です.
python/entrypoint.sh
#!/bin/bash
pip list
gunicorn django_comment.wsgi:application --bind 0.0.0.0:8000
Web サーバに関するサービスは docker-compose.yml の13行目以降に記載されています.今回は13行目のとおりサービス名に web を指定しましたが,以前のように nginx というようなサービス名であっても構いません.Nginx に関する Dockerfile は次のとおりです.
nginx/Dockerfile
FROM nginx:1.25.5
COPY conf.d/default.conf /etc/nginx/conf.d/default.conf
Nginx の設定ファイルは nginx/conf.d/defaul.conf として次の内容とします.ここで,1行目の upstream python と10行目の http://python では同じ名称 python を利用していることに注意してください.また,2行目の python_app:8000 の python_app は docker-compose.yml の2行目にあるサービス名と,10-11行目のポート番号に一致させていることにも注意してください.つまり,http://localhost/static/ 以外のリクエストは8000番ポートを利用して python_app コンテナに転送されることを意味しています.
nginx/conf.d/default.conf
upstream python {
server python_app:8000;
}
server {
listen 80;
server_name 0.0.0.0;
location / {
proxy_pass http://python;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
}
location /static/ {
alias /static/;
}
}
Web サービスへのアクセスログ (access.log と error.log) を保存する volume となる logs/ngins/ というディレクトリも作成します.しかしながら,ログファイル自体が Git のリポジトリに含まれないように .gitignore ファイルを作成しておきます.
logs/nginx/.gitignore
*
また,Django のプロジェクトは django ディレクトリに格納しますが,ここでもやはり .gitignore を作成しておきます.
django/.gitignore
*
それでは GitHub にある Django プロジェクトについて,リポジトリのクローンを作成します.まず,(1) では django ディレクトリに移動しています.(2) でクローンを作成します.これによりプロジェクトのすべてのファイルがダウンロードされます.(3) でクローンが作成されたことを確認します.クローンはリポジトリ名と同じ名前のディレクトリに作成されます.今回は docker-compose.yml の9行目のとおり,django/code/ ディレクトリにプロジェクトのファイルを設置することになっていることから,(4) でディレクトリ名を変更しています.(5) ではディレクトリ名が正しく変更されたことを確認しています.なお,ls や mv は Bash のコマンドであることから,Windows コマンドプロンプトの場合は dir と rn または rename を使用してください.
% cd django ⏎ # (1) ディレクトリを移動 % git clone https://github.com/rinsaka/django_comment2022.git ⏎ # (2) クローンを作成 Cloning into 'django_comment2022'... remote: Enumerating objects: 250, done. remote: Counting objects: 100% (250/250), done. remote: Compressing objects: 100% (105/105), done. remote: Total 250 (delta 139), reused 246 (delta 135), pack-reused 0 (from 0) Receiving objects: 100% (250/250), 34.30 KiB | 2.14 MiB/s, done. Resolving deltas: 100% (139/139), done. % ls ⏎ # (3) クローンが作成されたことを確認 django_comment2022 % mv django_comment2022 code ⏎ # (4) ディレクトリ名を変更 % ls ⏎ # (5) 変更されたことを確認 code %
Django プロジェクトのファイル構造を確認しておきます.
tree -F ⏎
./
└── code/
├── comments/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── fixtures/
│ │ └── comments-data.json
│ ├── forms.py
│ ├── migrations/
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ ├── models.py
│ ├── static/
│ │ └── css/
│ │ └── style.css
│ ├── templates/
│ │ ├── base.html
│ │ ├── comments/
│ │ │ ├── delete_confirm.html
│ │ │ ├── form.html
│ │ │ ├── index.html
│ │ │ └── show.html
│ │ ├── index.html
│ │ ├── prev_next.html
│ │ └── simple_pagination.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── db.sqlite3.sample
├── django_comment/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py*
├── readme.md
└── requirements.txt
10 directories, 29 files
%
別のコマンドでもファイルの一覧を確認しておきます.Django プロジェクトのディレクトリに manage.py などのファイルが存在することに注意してください.
% ls -la ⏎ total 400 drwxr-xr-x 11 rinsaka staff 352 10月 14 15:05 . drwxr-xr-x 4 rinsaka staff 128 10月 14 15:05 .. -rw-r--r-- 1 rinsaka staff 53248 10月 14 15:05 .coverage drwxr-xr-x 12 rinsaka staff 384 10月 14 15:05 .git -rw-r--r-- 1 rinsaka staff 71 10月 14 15:05 .gitignore drwxr-xr-x 14 rinsaka staff 448 10月 14 15:05 comments -rw-r--r-- 1 rinsaka staff 135168 10月 14 15:05 db.sqlite3.sample drwxr-xr-x 7 rinsaka staff 224 10月 14 15:05 django_comment -rwxr-xr-x 1 rinsaka staff 670 10月 14 15:05 manage.py -rw-r--r-- 1 rinsaka staff 439 10月 14 15:05 readme.md -rw-r--r-- 1 rinsaka staff 174 10月 14 15:05 requirements.txt %
2つ上の階層(Docker のディレクトリ)に戻っておきます.
% cd .. ⏎ % cd .. ⏎ %
すべてのファイルの準備ができたらいよいよコンテナを起動します.web サービス(nginxイメージ)がクラウドサービスからプルされ,django01sqlite-python_app イメージがビルドされたことが出力から分かります.その後,2つのコンテナが起動したことも読み取れます.
% docker compose up -d ⏎ [+] Running 8/8 ✔ web Pulled 7.9s ✔ 24c63b8dcb66 Pull complete 2.6s ✔ ac894f1d1dfb Pull complete 3.1s ✔ 2572d4eb2260 Pull complete 3.1s ✔ 0ac3805c647c Pull complete 3.1s ✔ da20f09652a8 Pull complete 3.1s ✔ 2de21a3abd85 Pull complete 3.1s ✔ 77cea143f3c3 Pull complete 3.2s [+] Building 18.9s (9/9) FINISHED => [internal] load local bake definitions 0.0s => => reading from stdin 598B 0.0s => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 385B 0.0s => [internal] load metadata for docker.io/library/python:3.11 2.6s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [1/3] FROM docker.io/library/python:3.11@sha256:a2bd92ce584000ca1a93aea40b7afa1 14.1s => => resolve docker.io/library/python:3.11@sha256:a2bd92ce584000ca1a93aea40b7afa13 0.0s => => sha256:a2bd92ce584000ca1a93aea40b7afa138171e8e2bfba59d2ac2a 10.32kB / 10.32kB 0.0s => => sha256:c29fe597c17ce398a04cf884bdfa0b42312fb5e8af6c0fdca67004 2.32kB / 2.32kB 0.0s => => sha256:c0bb6bc63b1ec99f8691645f22a462e7735b41a88808f8216c46b8 6.23kB / 6.23kB 0.0s => => sha256:28aec8b14b3eeacbf47ef38af72fab694b109fdcdf3158172275 49.65MB / 49.65MB 3.4s => => sha256:003e6ed58c0c6ddbc757cdcbd876d66b9b5eed39128088a0055c 25.02MB / 25.02MB 2.2s => => sha256:390c9631087ef516f060328537d417f223e1f264c968e3918689 67.58MB / 67.58MB 3.4s => => sha256:fa24803fc7a76e7898f7a43b6b4f9851f1150fe72077582125 226.07MB / 226.07MB 8.2s => => extracting sha256:28aec8b14b3eeacbf47ef38af72fab694b109fdcdf31581722750599baf 1.3s => => sha256:cef01e0402edb7321b12b5be495a7e1da3cecc4ec67d0b5acaedd1 6.20MB / 6.20MB 4.0s => => sha256:fbe585a92f31bac8761c765519ed760128e2e548d160dd1f5dac 23.50MB / 23.50MB 5.0s => => sha256:a46927c07bdb1349e9a75d0189f3d9391e8d75ca95ef931cc65c96a3ba 249B / 249B 4.5s => => extracting sha256:003e6ed58c0c6ddbc757cdcbd876d66b9b5eed39128088a0055c819ebe1 0.5s => => extracting sha256:390c9631087ef516f060328537d417f223e1f264c968e39186895696e78 1.8s => => extracting sha256:fa24803fc7a76e7898f7a43b6b4f9851f1150fe7207758212555ada9266 4.5s => => extracting sha256:cef01e0402edb7321b12b5be495a7e1da3cecc4ec67d0b5acaedd16bcdf 0.2s => => extracting sha256:fbe585a92f31bac8761c765519ed760128e2e548d160dd1f5dac1fc787f 0.6s => => extracting sha256:a46927c07bdb1349e9a75d0189f3d9391e8d75ca95ef931cc65c96a3bad 0.0s => [2/3] WORKDIR /code 0.2s => [3/3] RUN pip install --upgrade pip 1.8s => exporting to image 0.1s => => exporting layers 0.1s => => writing image sha256:aefe61bf7d8dabdf4baaa698f773040ed6561701c1e6bdbd3d1c582a 0.0s => => naming to docker.io/library/django01sqlite-python_app 0.0s => resolving provenance for metadata file 0.0s [+] Running 4/4 ✔ django01sqlite-python_app Built 0.0s ✔ Network django01sqlite_default Created 0.0s ✔ Container python_app_01 Started 0.2s ✔ Container python_nginx_01 Started 0.2s %
Docker イメージの一覧を確認します.上の作業でダウンロード(プル)やビルドされたイメージが表示されるはずです.なお,ビルドされたイメージの名称は ディレクトリ名-サービス名 になっていることに注意してください.
% docker image ls ⏎ REPOSITORY TAG IMAGE ID CREATED SIZE django01sqlite-python_app latest aefe61bf7d8d About a minute ago 1.13GB nginx 1.25.5 8dd77ef2d82e 17 months ago 193MB %
2つのコンテナが起動状態であることを確認します.
% docker container ls ⏎
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a24f5386aac4 nginx:1.25.5 "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:80->80/tcp, [::]:80->80/tcp python_nginx_01
dc103f45b919 django01sqlite-python_app "/bin/sh -c 'pip ins…" About a minute ago Up About a minute 8000/tcp python_app_01
%
起動中の Python アプリケーションサーバ(サービス名:python_app,コンテナ名:python_app_01,コンテナID:dc103f45b919)にログインためのコマンドは複数あります.次のいずれかのコマンドを利用してください.
% docker compose exec python_app /bin/bash ⏎ # (1) docker-compose.yml のサービス名 % docker exec -it python_app_01 /bin/bash ⏎ # (2) container name % docker exec -it dc103f45b919 /bin/bash ⏎ # (3)container ID
例えば (1) docker-compose.yuml のサービス名を指定してコンテナにログインします.ログイン後のワークディレクトリ(カレントディレクトリ)が /code になっています.これは Dockerfile の8行目にある WORKDIR /code の指定による結果です.また,ls コマンドを利用して,manage.py などのファイルが /code/ ディレクトリに存在していることを確認します.
% docker compose exec python_app /bin/bash ⏎ root@dc103f45b919:/code# ls ⏎ comments db.sqlite3.sample django_comment manage.py readme.md requirements.txt root@dc103f45b919:/code#
上の表示から SQLite データベースのファイル /code/db.sqlite が存在しないことが分かったので,データベースのマイグレーションとサンプルデータの投入を行います.次のコマンドではマイグレーションの実行状況を確認しています.まだマイグレーションファイルがまだ一切実行されていない(つまり,データベースにテーブルが存在しない)ことが分かります.
root@dc103f45b919:/code# python manage.py showmigrations ⏎
System check identified some issues:
WARNINGS:
?: (staticfiles.W004) The directory '/code/static' in the STATICFILES_DIRS setting does not exist.
admin
[ ] 0001_initial
[ ] 0002_logentry_remove_auto_add
[ ] 0003_logentry_add_action_flag_choices
auth
[ ] 0001_initial
[ ] 0002_alter_permission_name_max_length
[ ] 0003_alter_user_email_max_length
[ ] 0004_alter_user_username_opts
[ ] 0005_alter_user_last_login_null
[ ] 0006_require_contenttypes_0002
[ ] 0007_alter_validators_add_error_messages
[ ] 0008_alter_user_username_max_length
[ ] 0009_alter_user_last_name_max_length
[ ] 0010_alter_group_name_max_length
[ ] 0011_update_proxy_permissions
[ ] 0012_alter_user_first_name_max_length
comments
[ ] 0001_initial
contenttypes
[ ] 0001_initial
[ ] 0002_remove_content_type_name
sessions
[ ] 0001_initial
root@dc103f45b919:/code#
マイグレーションを実行して各種テーブルを生成します.
root@dc103f45b919:/code# python manage.py migrate ⏎
System check identified some issues:
WARNINGS:
?: (staticfiles.W004) The directory '/code/static' in the STATICFILES_DIRS setting does not exist.
Operations to perform:
Apply all migrations: admin, auth, comments, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying comments.0001_initial... OK
Applying sessions.0001_initial... OK
root@dc103f45b919:/code#
テーブルの生成ができたのでサンプルデータを投入します.
root@dc103f45b919:/code# python manage.py loaddata comments/fixtures/comments-data.json ⏎
System check identified some issues:
WARNINGS:
?: (staticfiles.W004) The directory '/code/static' in the STATICFILES_DIRS setting does not exist.
Installed 10 object(s) from 1 fixture(s)
root@dc103f45b919:/code#
データベースのファイル db.sqlite3 が生成されたことが分かります.(なお,このリポジトリにはサンプルのデータベースファイルとして db.sqlite3.sample というファイルも存在します.このサンプルデータにはすでにサンプルデータが投入されているので,マイグレーションとサンプルデータの投入を行う代わりに,このファイル名を変更する方法でも動作します.)
root@dc103f45b919:/code# ls ⏎ comments db.sqlite3.sample manage.py requirements.txt db.sqlite3 django_comment readme.md root@dc103f45b919:/code#
データベースの準備ができたのでコンテナからログアウトします.
root@dc103f45b919:/code# exit ⏎
%
すべての準備が整ったので,ブラウザで http://localhost/ に接続します.
comments アプリケーションに移動すると正しくシステムが動作していることが確認できるはずです.コメントの新規投稿や編集,削除などの機能を試してください.
なお,Django の内部ファイルを変更してもリアルタイムにはその変更が反映されません.例えば,django/code/comments/views.py ファイルを変更した後は次のようにコンテナを再起動する必要があります.
% docker compose restart python_app ⏎
[+] Restarting 1/1
✔ Container python_app_01 Started 10.2s
%
さらに,サービス(コンテナ)の起動や終了方法を確認しておきます.現時点では2つのコンテナが起動しており,すべてのコンテナを停止するには docker compose stop を実行します.
% docker container ls ⏎ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 57ff831ee93f nginx:1.25.5 "/docker-entrypoint.…" 4 minutes ago Up 4 minutes 0.0.0.0:80->80/tcp, [::]:80->80/tcp python_nginx_01 5ee89da6a0b9 django01sqlite-python_app "/bin/sh -c 'pip ins…" 4 minutes ago Up 4 minutes 8000/tcp python_app_01 % docker compose stop ⏎ [+] Stopping 2/2 ✔ Container python_nginx_01 Stopped 0.2s ✔ Container python_app_01 Stopped 10.1s %
起動中のコンテナがなくなったことを確認します.
% docker container ls ⏎
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
%
-a オプションを付けると終了したコンテナも表示されます.2つのコンテナが終了状態 (Exited) であることが分かりました.
% docker container ls -a ⏎
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
57ff831ee93f nginx:1.25.5 "/docker-entrypoint.…" 5 minutes ago Exited (0) 17 seconds ago python_nginx_01
5ee89da6a0b9 django01sqlite-python_app "/bin/sh -c 'pip ins…" 5 minutes ago Exited (137) 6 seconds ago python_app_01
%
もう一度すべてのコンテナを起動します.
% docker compose up -d ⏎ [+] Running 2/2 ✔ Container python_app_01 Started 0.1s ✔ Container python_nginx_01 Started 0.1s % docker container ls ⏎ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 57ff831ee93f nginx:1.25.5 "/docker-entrypoint.…" 5 minutes ago Up 12 seconds 0.0.0.0:80->80/tcp, [::]:80->80/tcp python_nginx_01 5ee89da6a0b9 django01sqlite-python_app "/bin/sh -c 'pip ins…" 5 minutes ago Up 12 seconds 8000/tcp python_app_01 %
今度は docker compose down コマンドを実行します.このコマンドを利用するとすべてのコンテナを終了させるだけでなくコンテナの破棄も行います.
% docker compose down ⏎ [+] Running 3/3 ✔ Container python_nginx_01 Removed 0.2s ✔ Container python_app_01 Removed 10.2s ✔ Network django01sqlite_default Removed 0.2s % docker container ls ⏎ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES % docker container ls -a ⏎ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES %
最後に Docker イメージを確認しておきます.これらがもう不要であれば削除しても構いません.(nginxのイメージは次のページでも再利用します.)
% docker image ls ⏎
REPOSITORY TAG IMAGE ID CREATED SIZE
django01sqlite-python_app latest aefe61bf7d8d 13 minutes ago 1.13GB
nginx 1.25.5 8dd77ef2d82e 17 months ago 193MB
%


