Let's Django!Let's Django!

這是 Django Tutorial 的第 11 篇,同時也是「Django 專案容器化」三部曲的第 1 篇。

範例程式碼可參考我的 GitHub 專案

本文相關的程式碼改動,都集中在這個 PR

系列:Django 專案容器化


環境不一致」是軟體開發中的常見困境,你用 Windows、我用 Mac,在我的機器上順利運行的程式碼,換到另一個人的電腦可能就不對了。

而「容器化」正是處理這類環境設定議題的主要手段。

容器化技術不僅一定程度解決了「在我的電腦可以執行」的老問題,更在不同面向上,改變了軟體開發的方式。

不止是開發,從測試到部署,容器都佔據了重要角色。

透過標準化的容器環境,團隊成員可以確保程式碼在不同環境中的表現一致

容器化開發

由此可見,「容器化」是現代開發的 ABC。

我更想強調是:哪怕只是在本機上運行,維持專案的容器化也是一個好習慣,方便日後遷移、分享,甚至協作。

本文是「Django 專案容器化」三部曲系列的第一篇,將帶你從零開始,將一個 Django 專案進行容器化。

下一篇則介紹如何將 Django 專案容器與的 db 容器整合,並使用 Docker Compose 建立多容器架構。

最後則是(拖稿許久的)Python 套件管理器——Poetry——的容器化設定,讓你正式告別requirements.txt

這些都是現代開發日常,值得我們一一了解並實踐。


本文主旨與目標讀者

本文會帶你手把手將一個 Django 專案進行容器化改造。

讓你在本地開發時,也透過 Docker 容器來運行專案 app。而不是常見的——在本機的 CLI 直接執行python manage.py runserver指令。

這些改造並不難,但仍然需要讀者對 Docker 有基礎的了解。

此外,任何軟體專案都可以容器化,選擇 Django 只是為了讓例子更具體,而且它是我相當熟悉的工具。

如果你已經在工作中使用 Docker,那本文將會是一個實用的示範。

Docker 與容器

Docker 是一個開源的容器化平台,它讓開發者能夠將應用程式與其依賴(執行環境)打包成一個獨立的元件,確保在大部分環境中都能一致地運行。

關於 Docker 的學習指引,可參考這篇〈Docker 新手入門:書與線上課程推薦〉,本文主要關注「實作面」。

Docker 核心概念

使用 Docker,需要了解以下重要概念:

  • Image:包含執行環境、作業系統和應用程式等等的定義,是容器的基礎。
  • Container:根據 image 啟動起來的執行單位,本身是一個 process。其特性是在 image 之上再建立一個「讀寫層」。
  • Dockerfile:定義如何建立 image 的檔案。(本文重點
  • Volume:容器的持久化儲存空間,可將資料獨立存放在主機上(而不是直接置於容器中),避免容器刪除時資料遺失。使用-v參數來設定。

這些概念環環相扣,形成了一個完整的容器生態系統。

接下來,讓我們實際動手,將這些概念應用在我們的 Django 專案中。


為 Django 專案建立 Dockerfile

想要將現有的 Django 專案容化器,就要從建立自己的 Docker Image 開始。

想要為專案建立專屬的 image,你需要自行定義 Dockerfile

我們一樣以範例專案為例,試著在「專案根目錄」新增一個 Dockerfile。

Dockerfile

下面是一個基礎的 Dockerfile,使用了 Python 的官方 image:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 使用 Python Image
FROM python:3.12-slim

# 設定工作目錄
WORKDIR /app

# 複製專案環境與相關檔案
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

# 啟動 Django 開發伺服器
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

非常簡單!

重點說明

  • FROM:選擇基礎環境,這裡使用較輕量的python:3.12-slim
  • WORKDIR:設定工作目錄,確保相關檔案、資源都在同一處。
  • COPY:複製檔案至容器內部。這裡使用了兩次,主要是為了「快取最佳化」。
  • RUN:執行指令——使用 pip 安裝 Python 套件。
  • CMD:定義啟動容器時要執行的指令,這裡用來啟動 Django 開發伺服器。

在容器內執行 Python 專案的一大特色,就是不需要再建立一個專案虛擬環境——因為容器本身就已經是一個隔離環境

容器內的 Python 及相關套件是專屬於這個容器的,不會與其他容器或系統產生衝突。這種環境隔離也是 Docker 容器化的核心優勢之一。


用 Dockerfile 建立 Image

Dockerfile 是自定義 image 的工具,或說設計圖

有了 Dockerfile 後,我們在專案根目錄下使用 build 指令來建立 image:

1
docker build -t my-django-app .

這個指令會根據 Dockerfile 的設定,建立一個名為my-django-app的 image。當然,這裡的名稱是自訂的。

執行後我得到了錯誤訊息,才發現我竟然還沒有為本專案建立requirements.txt😅

參考〈Python 套件管理器——Poetry 完全入門指南〉中的這段來將 Poetry 虛擬環境內容輸出為requirements.txt

或直接使用下列指令:

1
poetry export -f requirements.txt -o requirements.txt --without-hashes

貼心提醒:我們會在第三篇將 Poetry 一併容器化。至此之後,專案中就不再需要requirements.txt了。

建立 Image 並確認

新增requirements.txt後重新執行指令,得到下列成功結果:

1
2
3
4
5
6
7
8
9
10
11
...
=> [internal] load build context 0.4s
=> => transferring context: 1.20MB 0.4s
=> [2/5] WORKDIR /app 0.2s
=> [3/5] COPY requirements.txt . 0.0s
=> [4/5] RUN pip install -r requirements.txt 5.7s
=> [5/5] COPY . . 0.8s
=> exporting to image 0.5s
=> => exporting layers 0.5s
=> => writing image sha256:5dfec18599155b31991534edc194511e43400db93ca7831284277f41 0.0s
=> => naming to docker.io/library/my-django-app 0.0s

保險起見,還是先用指令docker image ls確認一下 image 真的存在。

1
2
3
4
❯ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
my-django-app latest 5dfec1859915 5 minutes ago 356MB
...

至此,image 的建立大功告成。接下來我們要把容器 run 起來。


運行 Docker 容器

使用下面 Docker 指令運行容器,並將容器內部的 8000 port 對應到主機:

1
docker run -v $(pwd)/db.sqlite3:/app/db.sqlite3 -p 8000:8000 my-django-app

除了 port mapping,我們還用了-v參數進行「bind mounts」,確保本機的 SQLite 資料庫檔案可以直接 mount 到容器中。

執行成功後,在瀏覽器中輸入 http://localhost:8000/hello/,就可以看到專案已經正常執行:

1
2
3
4
5
❯ docker run -v $(pwd)/db.sqlite3:/app/db.sqlite3 -p 8000:8000 my-django-app
Watching for file changes with StatReloader
[24/Jan/2025 08:52:47] "GET /hello/ HTTP/1.1" 200 28
[24/Jan/2025 08:52:52] "GET /hello/ HTTP/1.1" 200 28
[24/Jan/2025 08:52:54] "GET /hello/ HTTP/1.1" 200 28

PS:目前專案只有一個端點可以使用XD,那就是/hello/,回應如下:

1
2
3
4
5
// http://127.0.0.1:8000/hello/

{
"message": "Hello, world!"
}

目前的不足之處

這樣算是完成初步的容器化了,但說真的,如果只是做到這步,你可能會覺得這簡直比之前還不便!

不便之處有下。

問題一:修改專案程式碼後無法即時更新

在本機運行時,程式碼只要一改,服務就會自動更新(使用測試模式),最多也只要重啟就可以看到新的變動。

但容器中的程式碼並不會「自動同步」。

簡單暴力的方式,是將「整個專案目錄」內容都透過 bind mount 掛載到容器中:

1
docker run -p 8000:8000 -v $(pwd):/app my-django-app

而不僅僅是掛載db.sqlite3這個檔案。

問題二:每次都要執行「Docker 指令 + 參數」,好麻煩!

落落長的指令加參數,真的讓人很排斥,遠不如原來的python manage.py runserver指令簡潔。

如果每次重置環境都要輸入這些內容,會讓人動力大減。

放心,以上兩個問題都會在下一篇中改善——畢竟應該沒有人是這樣開發的吧?😅


小結與下一步

本文完成了對 Django 專案的基礎容器化,從建立 Dockerfile 到建立 Image,再到運行容器,一步步帶你體驗 Docker 的基本操作。

還處理了 SQLite 資料庫檔案的持久化問題。

不過,這些都只是「暫時」的做法。

在下一篇文章中,我們將更進一步:

  • 探討如何替換 SQLite 為生產級資料庫(如 PostgreSQL)。
  • 使用 Docker Compose 將 Django 與資料庫容器整合,建立一個多容器架構。

透過 Docker,為我們打造更流暢的現代開發體驗,敬請期待。