WSL2 + Ubuntu 22.04.1 な環境上のDockerへ、既存のDjangoアプリを載せてみた

前回の記事にて、個人アプリをDjangoを4.1にアップデートするついでに、DBをPostgreSQLからSQLiteへと移行しました。
https://thinkami.hatenablog.com/entry/2022/09/14/215942

DBをSQLiteに移行したことにより、DjangoアプリをDockerの1コンテナで起動できそうでした。

そこで、WSL2上のDockerに既存のDjangoアプリを載せてみたため、メモを残します。

 
目次

 

環境

 

準備

Dockerfileの作成

今回、Dockerで動作させるDjangoアプリは、WSL2上にあるDjangoアプリのソースコードCOPY して使うことにします。

あとは、Dockerの中で SQLite を使うので、 sqlite3 パッケージを追加します。

FROM python:3.10.7-slim

# Djangoアプリの8000番ポートを公開するおしらせ
EXPOSE 8000

# 必要なライブラリをインストール
# 実行時に `debconf: delaying package configuration, since apt-utils is not installed` が出るが、無視してよさそう
RUN apt-get update -qq \
    && apt-get install -y --no-install-recommends sqlite3 \
    && apt-get -y clean \
    && rm -rf /var/lib/apt/lists/*

# working directoryの設定
WORKDIR /app

# リポジトリのファイルをコピー
COPY . /app

# パッケージインストール
RUN pip install --no-cache-dir -r requirements.txt

# 起動
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

 

.dockerignore ファイルを用意

今回、WSL2上のリポジトリのファイル群をDocker内に COPY しています。

ただ、venv環境のファイルや .env ファイルなどはコピーしてほしくありません。

そこで、 .dockerignore ファイルを用意し、Dockerにコピーしてほしくないファイルを指定しました。

 
以下が今回の .dockerignore ファイルです。

# .env
.env
.env_example

# git
.git
.gitignore
.idea

# Docker
Dockerfile
.dockerignore

# DB dumps
dump.json
latest.dump

# venv
env
env_3_10_7
env_3_10_7_1

# pyenv
.python-version

# pytest
.pytest_cache
conftest.py
pytest.ini

# docs
*.md

 

Dockerfileを書く上でのメモ

Dockerfileでは apt ではなく apt-get を使う

apt を指定すると、Dockerイメージのビルド時に以下の警告が出ます。

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

 
apt(8)の以下の記載にあるように、スクリプトでは apt-get を使ったほうが良いようです。

apt(8) コマンドラインはエンドユーザ向けツールとして設計されています。動作はバージョン間で変更される可能性があります。後方互換性を損なうことのないようには努めますが、変更がインタラクティブな使用に有益と思われる場合には、その保証はありません。

apt(8) のすべての機能は、apt-get(8) や apt-cache(8) など専用の APT ツールで利用可能です。apt(8) は、単にいくつかのオプションのデフォルト値を変更します (apt.conf(5) の特にバイ ナリ範囲を参照)。可能な限り下位互換性を保つように、スクリプトでは (潜在的に有効になっているいくつかの追加オプションをつけて) コマンドを使うべきです。

https://manpages.ubuntu.com/manpages/jammy/ja/man8/apt.8.html

 
そのため、Dockerfile内でも apt-get を使うようにします。

 

警告: debconf: delaying package configuration, since apt-utils is not installed

上記と同じく、Dockerイメージをビルドしている時に

debconf: delaying package configuration, since apt-utils is not installed

という警告が出ました。

調べてみたところ、以下の記事にある通り、無視しても良さそうでした。
Dockerビルド時のエラーメッセージ debconf: delaying package configuration, since apt-utils is not installed | かきノート

そこで、今回は個人アプリということもあり、無視することにしました。

 

EXPOSEでDjangoアプリを起動するポートを指定

Dockerの公式ドキュメントには

EXPOSE 命令だけは、実際にはポートを 公開しません。これは、どのポートを公開する意図なのかという、イメージの作者とコンテナ実行者の両者に対し、ある種のドキュメントとして機能します。コンテナの実行時に実際にポートを公開するには、 docker run で -p フラグを使い、公開用のポートと割り当てる( マップする)ポートを指定します。

https://docs.docker.jp/engine/reference/builder.html#expose

とあります。

今回のDocker上のDjangoアプリは 8000 ポートで起動するため、 EXPOSE 8000 を指定しておきます。

 

apt-get install の --no-install-recommends について

今回 apt-get install 時に、以下の記事を参考にして --no-install-recommends を追加しています。

デフォルトだと recommends しているだけの必須ではないパッケージも一緒に入って時間がかかるので --no-install-recommends をつけるのが常套手段

debianパッケージ周りでよく使うコマンドとオプション - sonots:blog

 

ビルド用ライブラリは追加してないので、 apt-get remove は不要

今回のDjangoアプリではビルド用ライブラリを追加していないため、 apt-get remove は不要でした。

 

aptキャッシュはクリーンにする

Dockerの公式ドキュメントの「Dockerfile のベスト・プラクティス」には以下の記載がありました。

apt キャッシュをクリーンアップし /var/lib/apt/lists を削除するのは、イメージ容量を小さくするためです。そもそも apt キャッシュはレイヤー内に保存されません。RUN 命令は apt-get update から始めているので、 apt-get install の前に必ずパッケージのキャッシュが更新されます。

注釈 公式の DebianUbuntu のイメージは 自動的に apt-get clean を実行する ので、明示的にこのコマンドを実行する必要はありません。

Dockerfile のベスト・プラクティス — Docker-docs-ja 20.10 ドキュメント

 
そこで、今回は

&& apt-get -y clean \
&& rm -rf /var/lib/apt/lists/*

を追加しています。

なお、今回のベースイメージが slim なこともあり、念のため apt-get clean を実行しています。

ちなみに、 apt-get clean は以下の挙動のようです。

The same as above, except it removes all packages from the package cache. This may not be desirable if you have a slow Internet connection, since it will cause you to redownload any packages you need to install a program.

The package cache is in /var/cache/apt/archives .

...

AptGet/Howto - Community Help Wiki  
 

pip installで no-cache-dir してキャッシュを使わないようにする

以下にあるように、 pip install する時に --no-cache-dir を指定して、キャッシュを使用しないようにしました。
python - What is pip's --no-cache-dir good for? - Stack Overflow

なお、上記のコメントにある通り、Python 3.6.10 以降のイメージであれば PIP_NO_CACHE_DIR という設定も使えるようです。
python - How to suppress pip upgrade warning? - Stack Overflow

 

ビルド

いつも通りDockerイメージをビルドします。

$ docker build -t ringo-tabetter:0.1 .
...
Successfully built 8b90c3a7c141
Successfully tagged ringo-tabetter:0.1

 

Dockerコンテナを起動

$ docker run -p 8888:8000 ringo-tabetter:0.1

Watching for file changes with StatReloader

 

Dockerコンテナの中身を確認

余計なファイルが COPY されていないか、別ターミナルを開いて確認します。

.dockerignore で指定したファイルは COPY されていませんでした。

# コンテナIDの確認
$ docker ps
CONTAINER ID   IMAGE                COMMAND  ...
2f3d4fdb4e6b   ringo-tabetter:0.1   "python manage.py ru…" ...

# コンテナの中に入る
$ docker exec -i -t 2f3d4fdb4e6b bash
root@2f3d4fdb4e6b:/app# 

# ファイルを確認
root@2f3d4fdb4e6b:/app# ls -al
total 516
drwxr-xr-x 1 root root   4096 Sep 19 11:19 .
drwxr-xr-x 1 root root   4096 Sep 19 11:20 ..
-rw-r--r-- 1 root root   1076 Sep  8 14:07 LICENSE
drwxr-xr-x 2 root root   4096 Sep 14 12:30 __pycache__
-rw-r--r-- 1 root root   4374 Sep 11 13:07 apples.yaml
drwxr-xr-x 7 root root   4096 Sep 11 02:31 apps
drwxr-xr-x 1 root root   4096 Sep 18 09:37 dj_ringo_tabetter
-rw-r--r-- 1 root root    266 Sep 18 09:31 manage.py
-rw-rw-r-- 1 root root    541 Sep 18 09:01 requirements.txt
-rw-r--r-- 1 root root 471040 Sep 13 11:49 ringo.db
drwxr-xr-x 4 root root   4096 Sep  8 14:07 static
drwxr-xr-x 3 root root   4096 Sep  8 14:07 templates

 

動作確認

今回は Docker のポート 8000 を、ホストの 8888 で公開しています。

そこで、 http://127.0.0.1:8888/hc/total にアクセスすると、いつものアプリが表示されました。

これにより、WSL2上のDockerで動いていることが確認できました。

 

Dockerコンテナの停止と削除

必要に応じて、コンテナの停止と削除を行います。

# コンテナIDを確認
$ docker ps
CONTAINER ID   IMAGE                COMMAND ...
2f3d4fdb4e6b   ringo-tabetter:0.1   "python manage.py ru…"  ...

# コンテナを停止
$ docker stop 2f3d4fdb4e6b
2f3d4fdb4e6b

# コンテナを削除
$ docker rm 2f3d4fdb4e6b
2f3d4fdb4e6b

 

その他やったこと

WSL2 + Ubuntu 22.04.1 な環境のDockerにDjangoアプリを載せる以外に、今回やったこともメモしておきます。

 

requirements.txt から不要なパッケージを削除

Heroku + PostgreSQLで動かしていた時のパッケージがあったため、削除しました。

 

pytzを削除し、zoneinfoを使うよう修正

Django4系から pytz パッケージが非推奨となりました。
Django 4.0 主な変更点まとめ | ryu22eBlog

そのため、pytz を使っていた部分を修正し、テストコードでも zoneinfo を使うよう修正しました。

 

settings.pyを分割

Herokuで動かしていた時は settings.py は1つでした。

ただ、本来は各環境ごとに settings.py を用意するのが良さそうです。

そこで、以下を参考に、 settings.py を分割し、各環境のファイルを用意しました。
[Django] プロジェクト構成のベストプラクティスを探る - 2.設定ファイルを本番用と開発用に分割する - Qiita

 

ソースコード

Githubに上げました。
https://github.com/thinkAmi/dj_ringo_tabetter

今回のプルリクはこちらです。
https://github.com/thinkAmi/dj_ringo_tabetter/pull/17