2026年10月26日 星期一

Ubuntu 26.04:Apache + Django + MySQL 正式部署

系列文章: 1.在 Ubuntu 26.04 建立 Django 開發環境
2.Ubuntu 26.04:Apache + Django + MySQL 正式部署

以 Apache 2.4、mod_wsgi、Python 虛擬環境及 MySQL 8.4 部署 Django。這是 Django 官方文件所支援的正式部署方式。

一、部署架構

 
 
部署原則:

  • Apache 直接透過 mod_wsgi 執行 Django,不使用開發伺服器。

  • MySQL 只監聽本機,不對外公開 3306。

  • Django 專案由專用的系統帳號管理,Apache worker 不使用 root 權限。

  • HTTPS 使用 Let's Encrypt 憑證。

  • 機密資料與程式碼分開保存,並定期備份資料庫。


二、安裝 Apache、Python 與 MySQL

    1.更新系統:         $sudo apt update         $sudo apt upgrade -y
 2.安裝必要套件:
$sudo apt install -y apache2 libapache2-mod-wsgi-py3 mysql-server \
 python3 python3-venv python3-dev build-essential \
default-libmysqlclient-dev pkg-config libffi-dev libssl-dev \ git curl certbot python3-certbot-apache

3.
啟動服務並設定開機自動啟動:
$sudo systemctl enable --now apache2
$sudo systemctl enable --now mysql
        4.確認安裝:
         $python3 --version
         $python3 --version
         $apache2 -v
        $mysql --version
      $sudo apachectl -M | grep wsgi
        $sudo systemctl is-active apache2
         $sudo systemctl is-active mysql         5.注意:Ubuntu 26.04 的套件版本可能隨安全更新變動。這套做法使用 Ubuntu 套件庫提
           供的 mod_wsgi,因此 Python 虛擬環境應使用相同的系統 Python 版本,避免 ABI 不相
           容。

三、建立專用 Django 系統帳號與目錄


    1.建立專用帳號:
    $sudo adduser --system --group --home /srv/djangoapp \
  --no-create-home djangoapp

  $sudo mkdir -p /srv/djangoapp
  $sudo chown djangoapp:djangoapp /srv/djangoapp
  $sudo chmod 750 /srv/djangoapp

  2.建立 Python 虛擬環境:
  $sudo -u djangoapp python3 -m venv /srv/djangoapp/venv

  3.安裝 Django 與 MySQL 驅動:
  $sudo -u djangoapp /srv/djangoapp/venv/bin/python -m pip install \
--upgrade pip
  $sudo -u djangoapp /srv/djangoapp/venv/bin/pip install \
  "Django>=5.2,<5.3" "mysqlclient>=2.2.1,<3.0" \
  "python-dotenv>=1.0,<2.0"

這裡固定在 Django 5.2 LTS 系列,避免未經測試就跨越主要版本。正式上線前,仍應依照專案相容性測試及安全更新政策選定確切套件版本。       
  4.確認版本:
  $sudo -u djangoapp /srv/djangoapp/venv/bin/python -m django \
   --version
$sudo -u djangoapp /srv/djangoapp/venv/bin/python -c \
'import MySQLdb; print("MySQL driver OK")'  

四、建立 MySQL 資料庫與專用帳號
      1.執行 MySQL 安全設定:依照提示設定適合的安全選項。
        $sudo mysql_secure_installation
        2.
若採用 Ubuntu 常見的本機 socket 管理方式,可先用下列指令登入:
        $
sudo mysql
        3.
在 MySQL 提示符中建立資料庫及專用使用者:
        
CREATE DATABASE django_db CHARACTER SET utf8mb4
        COLLATE utf8mb4_unicode_ci;
        4.產生一組隨機資料庫密碼:
           複製產生的值,將下方 SQL 的請替換成隨機密碼 換成它:
         CREATE USER 'django_user'@'localhost'
IDENTIFIED BY '請替換成隨機密碼';
   GRANT ALL PRIVILEGES ON django_db.* TO 'django_user'@'localhost';
   EXIT; 
    此帳號只對 django_db 資料庫擁有權限,不使用 MySQL root 帳號執行 Django。GRANT ALL PRIVILEGES ON django_db.* 允許 Django 執行資料表建立與遷移等操作,適合這份單一專案範例。如果正式營運政策要求最小權限,應將部署遷移帳號與日常執行帳號分開。
    5.
確認 MySQL 沒有對公網監聽:
    $sudo ss -lntp | grep 3306
    
一般本機部署應只顯示 127.0.0.1:3306 或 Unix socket,而不應顯示0.0.0.0:3306 或公網 IP。

五、建立 Django 專案
    1.建立專案:
        $
sudo -u djangoapp /srv/djangoapp/venv/bin/django-admin \
  startproject config /srv/djangoapp

 2.
專案目錄如下:
  
/srv/djangoapp/
├── manage.py
├── config/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   ├── asgi.py
│   └── wsgi.py
 └── venv/  

3.建立機密設定檔:
 $sudo install -o root -g djangoapp -m 640 /dev/null /etc/djangoapp.env   

4.產生 Django SECRET_KEY:
 $openssl rand -base64 48

5.編輯設定檔:
$sudo nano /etc/djangoapp.env

6.填入以下內容,並將密碼及金鑰替換成你實際產生的值:
DJANGO_SECRET_KEY=替換成隨機金鑰
DJANGO_DEBUG=0
DJANGO_ALLOWED_HOSTS=example.com,www.example.com
DJANGO_CSRF_TRUSTED_ORIGINS=https://example.com,https://www.example.com

MYSQL_DATABASE=django_db
MYSQL_USER=django_user
MYSQL_PASSWORD=替換成前面建立的MySQL密碼   
將 example.com 換成自己的網域;如果不使用 www,可將它從設定中移除。

7.
確認機密檔案權限:
$sudo chown root:djangoapp /etc/djangoapp.env
$sudo chmod 640 /etc/djangoapp.env
這樣一般使用者無法讀取機密檔案,而 Django 專用帳號可以讀取。請勿把這份檔案放入 Git 或網站公開目錄。

六、設定 Django 連接 MySQL 與正式環境安全選項
以下會直接建立一份適用於這個範例的 config/settings.py。它會從 /etc/djangoapp.env 讀取機密資料。
$
sudo tee /srv/djangoapp/config/settings.py > /dev/null <<'PY'

import os from pathlib import Path from dotenv import load_dotenv BASE_DIR = Path(__file__).resolve().parent.parent load_dotenv("/etc/djangoapp.env") SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1" ALLOWED_HOSTS = [ host.strip() for host in os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",") if host.strip() ] CSRF_TRUSTED_ORIGINS = [ origin.strip() for origin in os.environ.get( "DJANGO_CSRF_TRUSTED_ORIGINS", "" ).split(",") if origin.strip() ] INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] ROOT_URLCONF = "config.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [BASE_DIR / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], }, }, ] WSGI_APPLICATION = "config.wsgi.application" DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": os.environ["MYSQL_DATABASE"], "USER": os.environ["MYSQL_USER"], "PASSWORD": os.environ["MYSQL_PASSWORD"], "HOST": "localhost", "PORT": "3306", "OPTIONS": { "charset": "utf8mb4", }, "CONN_MAX_AGE": 60, } } AUTH_PASSWORD_VALIDATORS = [ { "NAME": ( "django.contrib.auth.password_validation." "UserAttributeSimilarityValidator" ) }, { "NAME": ( "django.contrib.auth.password_validation." "MinimumLengthValidator" ) }, { "NAME": ( "django.contrib.auth.password_validation." "CommonPasswordValidator" ) }, { "NAME": ( "django.contrib.auth.password_validation." "NumericPasswordValidator" ) }, ] LANGUAGE_CODE = "zh-hant" TIME_ZONE = "Asia/Taipei" USE_I18N = True USE_TZ = True STATIC_URL = "/static/" STATIC_ROOT = BASE_DIR / "staticfiles" MEDIA_URL = "/media/" MEDIA_ROOT = Path("/var/lib/djangoapp/media") DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_CONTENT_TYPE_NOSNIFF = True X_FRAME_OPTIONS = "DENY" SECURE_REFERRER_POLICY = "same-origin" PY
將 HOST = "localhost" 用於本機 MySQL。Linux 上的 MySQL 驅動通
常會透過 Unix socket 連線;如果你的 MySQL 只接受 TCP,請將 HOST 改成 127.0.0.1,並確認帳號授權和 MySQL bind address 相符。

2.建立媒體檔案目錄:
$sudo mkdir -p /var/lib/djangoapp/media
$sudo chown djangoapp:djangoapp /var/lib/djangoapp/media
$sudo chmod 750 /var/lib/djangoapp/media


3.
將靜態檔案目錄預先建立好:
$sudo mkdir -p /srv/djangoapp/staticfiles
$sudo chown -R djangoapp:djangoapp /srv/djangoapp
$sudo chmod 755 /srv/djangoapp

4.
設定專案程式檔案的基本權限:
$sudo find /srv/djangoapp/config -type d -exec chmod 755 {} \;
$sudo find /srv/djangoapp/config -type f -exec chmod 644 {} \;
$sudo chmod 644 /srv/djangoapp/manage.py

七、設定 Django 首頁
建立一個簡單首頁,方便測試整個部署流程。
1.建立 config/views.py:
$sudo tee /srv/djangoapp/config/views.py > /dev/null <<'PY'
from django.http import HttpResponse

def home(request):
    return HttpResponse(
        "Django on Ubuntu 26.04 + Apache + MySQL is running.",
        content_type="text/plain; charset=utf-8",
    )
PY
2.修改 config/urls.py:
$sudo tee /srv/djangoapp/config/urls.py > /dev/null <<'PY'
from django.contrib import admin
from django.urls import path
from config.views import home

urlpatterns = [
    path("", home, name="home"),
    path("admin/", admin.site.urls),
]
PY
3.調整檔案權限:
$sudo chown djangoapp:djangoapp /srv/djangoapp/config/views.py \
  /srv/djangoapp/config/urls.py

$sudo chmod 644 /srv/djangoapp/config/views.py \
  /srv/djangoapp/config/urls.py
4.完成後,網站首頁預期會顯示:
Django on Ubuntu 26.04 + Apache + MySQL is running.


















   
 

沒有留言:

張貼留言

Ubuntu 26.04:Apache + Django + MySQL 正式部署

系列文章: 1.在 Ubuntu 26.04 建立 Django 開發環境 2.Ubuntu 26.04:Apache + Django + MySQL 正式部署 以 Apache 2.4、 mod_wsgi 、Python 虛擬環境及 MySQL 8.4 部署 D...