使用 Docker 封裝 Django APP 與部署¶
開始之前¶
任務目標
在這個任務中,你將學習:
- 使用環境變數管理敏感設定
- 設定 WhiteNoise 來服務靜態檔案
- 安裝 PostgreSQL 驅動程式
- 使用 Gunicorn 作為正式環境的 WSGI 伺服器
- 建立 Docker 映像檔來封裝 Django 專案
- 理解正式環境部署的最佳實踐
為什麼需要 Docker?¶
在開發 Django 專案時,我們經常會遇到「在我的電腦上可以執行」的問題。Docker 透過容器化技術,讓我們能夠將應用程式及其所有相依性打包在一起,確保在任何環境中都能一致地執行。
對於 Django 專案來說,Docker 特別有用,因為:
- 環境一致性:開發、測試、正式環境使用相同的設定
- 快速部署:一個指令就能啟動整個應用程式
- 依賴隔離:不同專案的依賴不會互相衝突
- 易於擴展:可以輕鬆地水平擴展應用程式
環境變數設定¶
在正式環境中,我們絕對不能把敏感資訊(如 SECRET_KEY、資料庫密碼)直接寫在程式碼裡。最佳實踐是使用環境變數來管理這些設定。
安裝 environs¶
environs 是一個方便的 Python 套件,可以幫助我們從環境變數讀取設定值,並提供型別轉換和驗證功能。
首先,安裝 environs[django]:
為什麼是 environs[django] 呢?
environs[django] 包含了針對 Django 設定最佳化的額外功能,例如 dj_db_url() 可以直接解析 DATABASE_URL 環境變數。
修改 settings.py¶
接下來,我們要修改 core/settings.py,讓它能從環境變數讀取設定。
首先,在檔案開頭加入 environs 的 import:
from pathlib import Path
from django.utils.translation import gettext_lazy as _
from environs import Env
env = Env()
env.read_env()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
先建立 Env 實例,用來讀取環境變數,接著呼叫 read_env() 會自動尋找專案根目錄的 .env 檔案並載入,不存在也不會失敗。
修改 DEBUG 設定:
從環境變數 DEBUG 讀取布林值,預設為 False。這樣在正式環境中,如果忘記設定 DEBUG,至少不會意外開啟除錯模式。
修改 SECRET_KEY 設定:
從環境變數 SECRET_KEY 讀取字串。這個設定是必填的,如果沒有設定會直接報錯。
修改 ALLOWED_HOSTS 設定:
從環境變數 ALLOWED_HOSTS 讀取列表,預設為空列表。env.list() 會自動將逗號分隔的字串轉換為列表,例如 ALLOWED_HOSTS=localhost,example.com 會變成 ["localhost", "example.com"]。
最後,修改 DATABASES 設定:
DATABASES = {
"default": env.dj_db_url(
"DATABASE_URL",
default=f"sqlite:///{BASE_DIR / 'db.sqlite3'}",
)
}
dj_db_url() 是 environs[django] 提供的特殊方法,可以直接解析 DATABASE_URL 環境變數,如果沒有設定 DATABASE_URL,預設使用 SQLite 資料庫。
DATABASE_URL 格式
DATABASE_URL 使用類似網址的格式來描述資料庫連線資訊:
- SQLite:
sqlite:///path/to/db.sqlite3 - PostgreSQL:
postgresql://username:password@host:port/database_name - MySQL:
mysql://username:password@host:port/database_name
建立 .env 檔案(開發用)¶
為了方便本地開發,我們可以建立一個 .env 檔案:
不要提交 .env 到版本控制
.env 檔案通常包含敏感資訊,絕對不要提交到 Git。請確保你的 .gitignore 包含 .env。
但通常我們會建立一個 .env.example 來跟其他開發者告知開發這個專案需要設定哪些環境變數
現在執行開發伺服器,確認一切正常:
WhiteNoise¶
為什麼需要 WhiteNoise?¶
在開發環境中,Django 的開發伺服器會自動服務靜態檔案。但在正式環境中,Django 並不擅長服務靜態檔案(Static Files),原因包括:
- 效能問題:Django 是用 Python 寫的,服務靜態檔案遠不如專門的 Web 伺服器(如 Nginx)有效率
- 安全性:Django 的
runserver指令明確標示「不適用於正式環境」 - 資源佔用:讓 Django 處理靜態檔案會浪費應用伺服器的資源
傳統的解決方案是使用 Nginx 或 Apache 來服務靜態檔案,但這會增加部署的複雜度。WhiteNoise 提供了一個更簡單的解決方案:它是一個 Python middleware,可以讓你的 Django 應用程式直接服務靜態檔案,而且效能接近專門的 Web 伺服器。
WhiteNoise 的優點:
- 部署簡單,不需要設定額外的 Web 伺服器
- 自動壓縮靜態檔案(gzip)
- 支援快取和版本控制
- 適合中小型專案,但如果你很在乎效能或是你的專案流量很大請務必使用 Nginx 等來服務靜態檔案。
安裝 WhiteNoise¶
設定 WhiteNoise¶
在 core/settings.py 中,將 WhiteNoiseMiddleware 加入 MIDDLEWARE,並且要放在 SecurityMiddleware 之後:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
WhiteNoise 必須放在 SecurityMiddleware 之後,這樣才能正確處理安全性標頭
接著,在 STATIC_ROOT 設定之後,加入 WhiteNoise 的設定:
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
CompressedManifestStaticFilesStorage 會自動壓縮靜態檔案,並在檔名中加入 hash 值,方便做快取控制
現在執行 collectstatic 指令來測試:
你會看到 WhiteNoise 將靜態檔案收集到 assets/ 目錄,並且自動產生壓縮版本。
安裝 PostgreSQL 驅動程式¶
雖然 SQLite 在開發環境很方便,但在正式環境中,我們通常會使用更強大的資料庫系統,如 PostgreSQL。即使你現在不打算立即使用 PostgreSQL,提前安裝驅動程式可以讓 Docker 映像檔更有彈性。
安裝 psycopg(PostgreSQL 的 Python 驅動程式):
psycopg vs psycopg2
psycopg(又稱 psycopg3)是新一代的 PostgreSQL 驅動程式,效能更好、支援更多功能。如果你看到其他教學使用 psycopg2,那是舊版本。
[binary] 表示安裝預先編譯的二進位版本,安裝速度較快。
安裝 Gunicorn¶
Django 的開發伺服器(runserver)只適合開發使用,在正式環境中,我們需要一個正式的 WSGI 伺服器。
Gunicorn(Green Unicorn)是一個流行的 Python WSGI HTTP 伺服器,特點包括:
- 輕量且高效能
- 支援多個 worker process,可以處理並發請求
- 設定簡單
- 廣泛用於正式環境
安裝 Gunicorn:
你可以測試執行 Gunicorn:
core.wsgi:指向我們專案的 WSGI 應用程式--workers 4:使用 4 個 worker process--bind 0.0.0.0:8000:監聽所有網路介面的 8000 埠
Worker 數量該如何設定?
一個常見的經驗法則是:worker 數量 = (2 × CPU 核心數) + 1
例如,如果你的伺服器有 2 個 CPU 核心,可以設定 5 個 workers。
現在可以按 Ctrl+C 停止 Gunicorn,我們接下來會在 Docker 中使用它。
建立 Docker 映像檔¶
現在我們已經安裝了所有必要的套件,接下來要建立 Docker 映像檔來封裝整個專案。
建立 Dockerfile¶
在專案根目錄建立 Dockerfile:
FROM ghcr.io/astral-sh/uv:python3.14-trixie-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
ENV UV_NO_SYNC=1
RUN \
apt-get update && \
apt-get install -y gettext && \
\
uv sync --no-default-groups --locked && \
\
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY . .
COPY --chmod=755 run-server ./
EXPOSE 8000
CMD [ "/app/run-server" ]
Dockerfile 重點:
- 使用 Astral 官方的 uv 映像檔,基於 Python 3.14 和 Debian Trixie
- 設定工作目錄為
/app - 先複製依賴定義檔案,利用 Docker 的層級快取,當依賴沒變動時可以加快建置速度
- 設定環境變數,告訴 uv 不要自動同步虛擬環境
- 安裝
- 更新 apt 套件列表,並安裝
gettext工具,用於編譯翻譯檔案(compilemessages) - 使用 uv 安裝專案依賴,
--no-default-groups表示不安裝開發用的依賴群組,--locked確保使用 lock 檔案中的版本 - 清理 apt 快取,減少映像檔大小
- 更新 apt 套件列表,並安裝
- 複製所有專案檔案到容器中
- 複製啟動腳本,並設定可執行權限
- 聲明服務會跑在哪個 port 上
- 設定容器啟動時執行的指令
建立 .dockerignore¶
.dockerignore 用來指定哪些檔案不要複製到 Docker 映像檔中:
# Ignore all files
*
# Allow list
## uv
!pyproject.toml
!uv.lock
## Python files
!**/*.py
## Translation files
!**/*.po
## Static files
!**/*.html
!**/*.css
!**/*.js
!static/
## Locale files
!locale/
## Scripts
!run-server
# But not these files
grid_demo.html
這個 .dockerignore 採用「預設拒絕,明確允許」的策略,可以確保不會意外把不需要的檔案打包進去。
建立 run-server 腳本¶
在專案根目錄建立 run-server 腳本,這是容器啟動時會執行的入口點:
#!/bin/bash
set -euo pipefail # (1)!
# Run migrations
uv run manage.py migrate # (2)!
# Collect static files
uv run manage.py collectstatic --noinput # (3)!
# If DJANGO_SUPERUSER_USERNAME, DJANGO_SUPERUSER_EMAIL and DJANGO_SUPERUSER_PASSWORD are set, create a superuser
if [ -n "${DJANGO_SUPERUSER_USERNAME:-}" ] && [ -n "${DJANGO_SUPERUSER_EMAIL:-}" ] && [ -n "${DJANGO_SUPERUSER_PASSWORD:-}" ]; then # (4)!
uv run manage.py createsuperuser --noinput || true
fi
# Compile messages
uv run manage.py compilemessages # (5)!
# Run server
uv run gunicorn core.wsgi --workers 4 --bind 0.0.0.0:8000 # (6)!
- 設定 bash 錯誤處理:
-e遇到錯誤立即停止,-u使用未定義變數時報錯,-o pipefail管線中任何指令失敗就返回失敗 - 執行資料庫遷移,確保資料庫結構是最新的
- 收集靜態檔案到
STATIC_ROOT,--noinput表示不要詢問確認 - 如果環境變數有設定超級使用者的資訊,自動建立超級使用者,如果建立失敗也沒關係
- 編譯翻譯訊息檔案
- 啟動 Gunicorn 伺服器
建置 Docker 映像檔¶
現在可以建置 Docker 映像檔了:
指令說明
docker build:建置 Docker 映像檔-t django-playground:為映像檔命名為django-playground.:使用當前目錄作為建置上下文
建置完成後,可以查看映像檔:
執行 Docker 容器¶
建立一個 .env.docker 檔案,用於 Docker 容器的環境變數:
DEBUG=False
SECRET_KEY=your-secret-key-change-this-in-production
ALLOWED_HOSTS=*
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_EMAIL=admin@example.com
DJANGO_SUPERUSER_PASSWORD=password
正式環境的 SECRET_KEY
在正式環境中,請務必更換成一個隨機、安全的 SECRET_KEY。你可以使用以下 Python 指令來產生:
如果有安裝 django-extensions 的話可以使用下方指定
現在可以執行容器:
指令說明
docker run:執行容器--rm:容器停止後自動刪除-p 8000:8000:將容器的 8000 埠對應到主機的 8888 埠--env-file .env.docker:從檔案載入環境變數django-playground:要執行的映像檔名稱
現在開啟瀏覽器,造訪 http://localhost:8888,你應該會看到你的 Django 應用程式正在執行!
如何進入容器進行除錯?
如果需要進入容器查看檔案或執行指令,可以使用:
這會啟動容器並開啟一個互動式的 bash shell。
使用 PostgreSQL 資料庫¶
如果你想在 Docker 中使用 PostgreSQL,可以修改 .env.docker:
DEBUG=False
SECRET_KEY=your-secret-key-change-this-in-production
ALLOWED_HOSTS=*
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_EMAIL=admin@example.com
DJANGO_SUPERUSER_PASSWORD=password
DATABASE_URL=postgresql://postgres:postgres@db:5432/django_playground
然後使用 Docker Compose 來同時啟動 Django, PostgreSQL 和 Adminer(資料庫管理工具):
name: django
services:
adminer:
image: adminer:5
restart: always
ports:
- 9999:8080
db:
image: postgres:18
environment:
POSTGRES_DB: django_playground
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql
web:
image: django-playground
ports:
- 8888:8000
env_file:
- .env.docker
depends_on:
- db
volumes:
postgres_data:
啟動服務背景:
看 log
停止服務:
任務結束¶
完成!
恭喜你完成了這個任務!現在你已經學會:
- 使用環境變數管理敏感設定
- 設定 WhiteNoise 來服務靜態檔案
- 安裝 PostgreSQL 驅動程式
- 使用 Gunicorn 作為正式環境的 WSGI 伺服器
- 建立 Docker 映像檔來封裝 Django 專案
- 理解正式環境部署的最佳實踐
你的 Django 專案現在已經準備好部署到任何支援 Docker 的平台了!