Tập này tách file settings duy nhất của ứng dụng quản lý chi tiêu thành ba môi trường dev, staging và production, đồng thời chuyển mọi secret sang biến môi trường bằng django-environ. Kết quả: cùng một codebase chạy ở cả ba môi trường, không còn secret nào trong code. Nối tiếp tập 1 đã lập checklist production-readiness, đây là bước đầu tiên biến checklist đó thành hiện thực.

Vì sao không dùng một settings.py cho mọi môi trường

Khi mọi môi trường dùng chung một file settings, bạn buộc phải viết các điều kiện if DEBUG rải rác khắp nơi, và chỉ một lần quên cũng đủ để debug bật trên production. Tách settings theo môi trường giúp mỗi môi trường khai báo rõ ràng những gì nó cần, còn phần dùng chung nằm ở một file base duy nhất.

Cấu trúc thư mục settings

Thay file settings.py bằng một package settings đặt cạnh manage.py, gồm một file base và một file cho mỗi môi trường:

expense_project/
├── manage.py
└── expense_project/
├── __init__.py
├── settings/
│ ├── __init__.py
│ ├── base.py
│ ├── dev.py
│ ├── staging.py
│ └── production.py
└── urls.py

Cài đặt django-environ và viết settings/base.py

Cài django-environ rồi chuyển cấu hình dùng chung vào base.py. Lưu ý: SECRET_KEY không có giá trị mặc định thật — thiếu biến môi trường thì ứng dụng lỗi ngay khi khởi động, thay vì âm thầm chạy với secret giả.

# settings/base.py
import environ
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
env = environ.Env()
environ.Env.read_env(BASE_DIR / ".env")
SECRET_KEY = env("DJANGO_SECRET_KEY")
DEBUG = env.bool("DJANGO_DEBUG", default=False)
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[])
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"expenses",
]
DATABASES = {"default": env.db("DATABASE_URL")}

Hàm env.db() hiểu chuỗi kết nối dạng postgres://user:pass@host:5432/dbname và trả về đúng dict cho DATABASES, nên đổi cơ sở dữ liệu chỉ là đổi một biến môi trường.

settings cho dev, staging và production

Mỗi file môi trường chỉ import từ base và ghi đè phần khác biệt. Dev bật debug và dùng SQLite cho nhanh, production tắt debug và bật các cờ bảo mật mà tập 11 sẽ nói kỹ hơn.

# settings/dev.py
from .base import * # noqa: F401,F403
DEBUG = True
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# settings/production.py
from .base import * # noqa: F401,F403
DEBUG = False
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

File .env và nguyên tắc không commit secret

Mỗi môi trường có một file .env riêng, không bao giờ đưa lên Git. Thêm .env vào .gitignore, và commit file .env.example chỉ chứa tên biến và giá trị mẫu. Khi cần xoay secret, chỉ sửa file .env trên server rồi khởi động lại tiến trình.

# .env.example — commit file này, KHÔNG commit .env thật
DJANGO_SECRET_KEY=thay-bang-secret-that
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=example.com,www.example.com
DATABASE_URL=postgres://user:pass@localhost:5432/expense_prod

Chạy ứng dụng với từng môi trường

Django chọn settings qua DJANGO_SETTINGS_MODULE, nên không cần sửa manage.py hay wsgi.py cho từng môi trường. Kiểm tra nhanh bằng python manage.py check --deploy với settings production để quét các cấu hình bảo mật trước khi đưa ứng dụng lên server thật.

# Dev
DJANGO_SETTINGS_MODULE=expense_project.settings.dev python manage.py runserver
# Production
DJANGO_SETTINGS_MODULE=expense_project.settings.production gunicorn expense_project.wsgi:application

Áp dụng vào dự án của bạn

Bắt đầu từ ứng dụng quản lý chi tiêu: tách settings, chuyển SECRET_KEY và DATABASE_URL sang .env, rồi chạy check --deploy để xem còn thiếu gì. Nếu dự án vẫn dùng SQLite trên production, đừng vội đổi cơ sở dữ liệu ngay — tập 4 sẽ lo phần PostgreSQL, còn hôm nay chỉ cần tách xong settings và sạch secret trong Git là đạt mục tiêu.

Bài viết liên quan

Câu hỏi thường gặp

Có nên để file .env trên server production không?

Có, nhưng chỉ trên server, chỉ tài khoản chạy ứng dụng đọc được (quyền file 600). Không commit .env vào Git, không gửi qua chat hay email, không đóng gói vào image Docker — secret được cấp cho container lúc chạy, điều tập 16 sẽ trình bày.

django-environ và python-decouple khác nhau thế nào?

Cả hai đều đọc biến môi trường, nhưng django-environ có thêm env.db() và env.cache() để parse chuỗi kết nối thành dict cấu hình Django ngay lập tức. Chỉ cần vài biến đơn giản thì python-decouple cũng đủ; có DATABASE_URL thì django-environ tiện hơn rõ rệt.

Quên đặt DJANGO_SETTINGS_MODULE thì sao?

Django sẽ dùng settings mặc định trong manage.py và wsgi.py — thường là settings cũ đã xóa — nên ứng dụng lỗi ngay khi khởi động. Đặt biến này trong unit file systemd hoặc lệnh chạy của process manager, rồi kiểm tra bằng python manage.py check trước mỗi lần deploy.

Nguồn tham khảo

  • Tài liệu django-environ, tài liệu Django về settings, The Twelve-Factor App (mục Config).