Django 从 0 到 1 打造完整电商平台:使用 Docker 容器化电商项目

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在公众号、今日头条持续发布最新文章,助你少走弯路。


大家好,我是IT策士。上一篇我们用 Nginx + uWSGI 手动部署了电商项目,但每换一台服务器就要重新安装 Python、配置环境、改一堆路径,整个过程繁琐又容易出错。今天,我们直接迈入现代部署的标准答案——Docker 容器化

Docker 能把你整个应用和环境打包成一个“集装箱”,做到一次构建,处处运行。不管是在你的开发机、测试服务器,还是云平台,只要装上 Docker,一行命令就能让整个电商系统跑起来。更妙的是,我们还不用再手动管理多个进程——Docker Compose 能编排 Nginx、Django、Celery、Redis、PostgreSQL 等所有服务,一键启动,一键停止。今天就带大家把我们的电商平台完整容器化。


一、为什么要容器化?

回顾上一节的部署流程:装系统依赖、创建虚拟环境、装包、配 uWSGI、配 Nginx、配 systemd……服务器越多,工作量翻倍。Docker 解决了三大痛点:

  1. 环境一致性:“我这能跑,你那不行”成为历史。Docker 镜像保证了开发、测试、生产环境完全相同。

  2. 快速部署:新服务器只需安装 Docker,拉取镜像即可运行,不用再踩环境坑。

  3. 服务编排:Docker Compose 把 Nginx、Django、Celery、Redis、数据库定义在一个 YAML 文件里,一条命令全部启动。

容器化架构图:

浏览器
  ↓
Nginx 容器 (80端口)  ← 处理静态文件,反向代理
  ↓
Django + uWSGI 容器 (内网,8000端口)
  ↓
PostgreSQL 容器 (5432)   Redis 容器 (6379)
  ↑
Celery Worker 容器 (连接 Redis)

每个服务一个容器,相互独立又协同工作。


二、准备项目:生产级数据库与配置

容器化后,SQLite 不适合多容器并发访问,我们将数据库升级为 PostgreSQL。同时,配置从环境变量读取,便于在不同环境切换。

2.1 修改 settings_production.py

在项目配置目录 django_ecommerce/ 下已有 settings_production.py(第 27 篇创建),我们需要改成通过环境变量配置数据库和 Redis:

import os
from .settings import *

DEBUG = os.getenv('DJANGO_DEBUG', 'False') == 'True'
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'change-me-in-production')
ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', '*').split(',')

# PostgreSQL 数据库(容器内通过服务名连接)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.getenv('POSTGRES_DB', 'django_ecommerce'),
        'USER': os.getenv('POSTGRES_USER', 'django_user'),
        'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'django_pass'),
        'HOST': os.getenv('DB_HOST', 'db'),        # docker-compose 中的服务名
        'PORT': os.getenv('DB_PORT', '5432'),
    }
}

# Redis 配置
CELERY_BROKER_URL = f"redis://{os.getenv('REDIS_HOST', 'redis')}:6379/0"
CELERY_RESULT_BACKEND = f"redis://{os.getenv('REDIS_HOST', 'redis')}:6379/1"
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': f"redis://{os.getenv('REDIS_HOST', 'redis')}:6379/2",
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        },
    }
}

# 静态文件
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'

# 媒体文件
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

注意:数据库引擎需要 psycopg2,稍后在 Dockerfile 中安装。

2.2 创建环境变量文件

在项目根目录创建 .env(不要提交到 Git):

DJANGO_DEBUG=False
DJANGO_SECRET_KEY=your-ultra-secret-key-here
DJANGO_ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com,localhost,127.0.0.1
POSTGRES_DB=django_ecommerce
POSTGRES_USER=django_user
POSTGRES_PASSWORD=strong_password_123
DB_HOST=db
REDIS_HOST=redis

三、编写 Dockerfile

在项目根目录创建 Dockerfile,用于构建 Django 应用镜像:

# 基础镜像
FROM python:3.11-slim

# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    DJANGO_SETTINGS_MODULE=django_ecommerce.settings_production

# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 创建应用目录
WORKDIR /app

# 安装 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt uwsgi psycopg2-binary

# 复制项目代码
COPY . .

# 收集静态文件
RUN python manage.py collectstatic --noinput

# 创建非 root 用户
RUN useradd -m django && chown -R django:django /app
USER django

# 暴露端口(uWSGI 将在 8000 端口监听)
EXPOSE 8000

# 默认命令:启动 uWSGI
CMD ["uwsgi", "--ini", "uwsgi_docker.ini"]

这里使用了 uwsgi_docker.ini,不同于之前的 Unix socket,容器中我们直接监听 TCP 端口,便于 Nginx 容器连接。

在项目根目录创建 uwsgi_docker.ini

[uwsgi]
chdir = /app
module = django_ecommerce.wsgi:application
master = true
processes = 4
threads = 2
socket = :8000
buffer-size = 32768
vacuum = true
uid = django
gid = django

四、编写 Nginx 配置(容器版)

在项目根目录创建 nginx/nginx.conf

upstream django {
    server web:8000;   # web 是 Django 容器在 docker-compose 中的服务名
}

server {
    listen 80;
    server_name _;

    location /static/ {
        alias /app/staticfiles/;
        expires 30d;
    }

    location /media/ {
        alias /app/media/;
        expires 7d;
    }

    location / {
        uwsgi_pass web:8000;     # 直接通过 HTTP 代理,因为 uWSGI 监听 8000
        include uwsgi_params;
        # 如果使用 uwsgi 协议:uwsgi_pass web:8000; include uwsgi_params;
    }

    client_max_body_size 10M;
}

但注意:上面我们用 uwsgi_pass 会尝试 uwsgi 协议。由于 uWSGI 监听的是 HTTP 端口(socket = :8000),这里应该用 proxy_pass http://web:8000; 或者改用 uWSGI 协议监听。我们统一用 HTTP 代理模式,简单通用。修改配置:

server {
    listen 80;
    server_name _;

    location /static/ {
        alias /app/staticfiles/;
        expires 30d;
    }

    location /media/ {
        alias /app/media/;
        expires 7d;
    }

    location / {
        proxy_pass http://web:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    client_max_body_size 10M;
}

并且 uWSGI 的 socket = :8000 需要改为 http-socket = :8000,或者直接用 HTTP 协议:protocol = http

更新 uwsgi_docker.ini

[uwsgi]
chdir = /app
module = django_ecommerce.wsgi:application
master = true
processes = 4
threads = 2
http = :8000          # 直接使用 HTTP 协议
buffer-size = 32768
vacuum = true
uid = django
gid = django

五、编写 docker-compose.yml

这是编排所有服务的核心文件。在项目根目录创建 docker-compose.yml

version: '3.8'

services:
  # PostgreSQL 数据库
  db:
    image: postgres:15
    container_name: ecom_db
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-django_ecommerce}
      POSTGRES_USER: ${POSTGRES_USER:-django_user}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-django_pass}
    ports:
      - "5432:5432"   # 可选,方便本地调试
    restart: unless-stopped

  # Redis
  redis:
    image: redis:7-alpine
    container_name: ecom_redis
    volumes:
      - redis_data:/data
    restart: unless-stopped

  # Django + uWSGI 应用
  web:
    build: .
    container_name: ecom_web
    command: uwsgi --ini /app/uwsgi_docker.ini
    volumes:
      - .:/app
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    environment:
      - DJANGO_DEBUG=${DJANGO_DEBUG:-False}
      - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
      - DJANGO_ALLOWED_HOSTS=${DJANGO_ALLOWED_HOSTS}
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - DB_HOST=db
      - REDIS_HOST=redis
    depends_on:
      - db
      - redis
    restart: unless-stopped

  # Celery Worker
  celery:
    build: .
    container_name: ecom_celery
    command: celery -A django_ecommerce worker -l info
    volumes:
      - .:/app
    environment:
      - DJANGO_DEBUG=${DJANGO_DEBUG:-False}
      - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - DB_HOST=db
      - REDIS_HOST=redis
    depends_on:
      - db
      - redis
    restart: unless-stopped

  # Nginx
  nginx:
    image: nginx:1.25
    container_name: ecom_nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    depends_on:
      - web
    restart: unless-stopped

# 命名卷
volumes:
  postgres_data:
  redis_data:
  static_volume:
  media_volume:

关键点:

  • 数据库和 Redis 数据通过命名卷持久化,容器删除数据不丢失。

  • Django 应用通过 build: . 从 Dockerfile 构建。

  • depends_on 确保启动顺序,但不保证数据库完全就绪,我们可以在 web 启动前执行迁移命令,或使用 wait-for-it.sh。后面会用一个初始化脚本处理。

  • Nginx 静态文件通过共享卷 static_volume 访问,这是 Django 收集后的静态文件。


六、处理数据库迁移和初始化

我们需要在 Django 容器首次启动时自动执行迁移和创建超级用户。创建一个初始化脚本 entrypoint.sh

#!/bin/sh
set -e

# 等待数据库就绪
echo "Waiting for PostgreSQL..."
while ! nc -z db 5432; do
  sleep 1
done
echo "PostgreSQL started"

# 迁移
python manage.py migrate --noinput

# 初始化商品数据(仅首次,加判断)
python manage.py shell -c "from products.models import Category; print('ok')" 2>/dev/null || python manage.py init_product_data

# 创建超级用户(若不存在)
python manage.py shell -c "
from django.contrib.auth import get_user_model;
User = get_user_model();
if not User.objects.filter(username='admin').exists():
    User.objects.create_superuser('admin', 'admin@example.com', 'admin123')
"

# 启动 uWSGI
exec uwsgi --ini /app/uwsgi_docker.ini

在 Dockerfile 中复制并启用这个脚本:

...
COPY . .
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
...
CMD ["/app/entrypoint.sh"]

同时,entrypoint.sh 需要 netcat 工具来检测数据库端口。在 Dockerfile 中安装:

RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc libpq-dev netcat-openbsd && rm -rf /var/lib/apt/lists/*

七、构建和启动容器

确保 .envnginx/nginx.confentrypoint.sh 已就绪。在项目根目录执行:

docker-compose up -d --build

-d 表示后台运行,--build 强制重新构建镜像。

控制台输出示例:

[+] Building 15.2s (15/15) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 567B
 => [internal] load .dockerignore
 ...
 => [web] exporting to image
 => => exporting layers
 => => writing image sha256:abc123...
 => [web] naming to docker.io/library/django_ecommerce_web

[+] Running 6/6
 ⠿ Network django_ecommerce_default   Created
 ⠿ Container ecom_redis               Started
 ⠿ Container ecom_db                  Started
 ⠿ Container ecom_web                 Started
 ⠿ Container ecom_celery              Started
 ⠿ Container ecom_nginx               Started

查看运行状态:

控制台输出:

NAME                COMMAND                  SERVICE             STATUS
ecom_db             "docker-entrypoint.s…"   db                  Up 2 minutes
ecom_redis          "docker-entrypoint.s…"   redis               Up 2 minutes
ecom_web            "/app/entrypoint.sh"     web                 Up 2 minutes
ecom_celery         "celery -A django_ec…"   celery              Up 2 minutes
ecom_nginx          "/docker-entrypoint.…"   nginx               Up 2 minutes (healthy)

八、查看日志与调试

查看所有服务的日志(类似 tail -f):

查看特定服务日志:

docker-compose logs -f web      # Django 日志
docker-compose logs -f celery   # Celery 日志
docker-compose logs -f nginx    # Nginx 日志

控制台输出示例(web 服务初始化日志):

ecom_web | Waiting for PostgreSQL...
ecom_web | PostgreSQL started
ecom_web | Operations to perform:
ecom_web |   Apply all migrations: admin, auth, cart...
ecom_web | Running migrations:
ecom_web |   Applying contenttypes.0001_initial... OK
...
ecom_web | Superuser created successfully.
ecom_web | [uWSGI] getting INI configuration from /app/uwsgi_docker.ini
ecom_web | *** Starting uWSGI 2.0.23 (64bit) ***
ecom_web | spawned uWSGI worker 1 (pid: 11)

九、验证部署

浏览器访问 http://服务器IPhttp://localhost(如果是本地 Docker),可以看到电商首页。

验证静态文件:图片、CSS、JS 正常加载。

验证动态功能

  • 注册新用户(终端可查看 Celery 异步发送邮件日志);

  • 浏览商品;

  • 加入购物车,下单。

验证支付:需要支付宝沙箱回调地址指向服务器公网 IP 或域名(可能需要配置 ALIPAY_RETURN_URLALIPAY_NOTIFY_URL)。可以在 .env 中增加相应变量,并在 settings_production.py 中读取环境变量:

ALIPAY_RETURN_URL = os.getenv('ALIPAY_RETURN_URL', 'http://localhost/payment/return/')
ALIPAY_NOTIFY_URL = os.getenv('ALIPAY_NOTIFY_URL', 'http://localhost/payment/notify/')

常用管理命令:

  • 进入容器 Shell:docker-compose exec web bash

  • 执行 Django 管理命令:docker-compose exec web python manage.py createsuperuser

  • 停止所有服务:docker-compose down

  • 停止并删除数据卷(⚠️ 会清空数据库):docker-compose down -v


十、生产环境注意事项

  1. 密钥管理:生产环境需使用真实的复杂密钥,避免将 .env 提交到仓库。可以使用 Docker secrets 或外部配置工具。

  2. 静态/媒体文件存储:容器化环境中,媒体文件最好使用对象存储(如 S3),避免文件丢失。对于小规模项目,挂载本地卷即可。

  3. 数据库备份:定期备份 PostgreSQL 数据卷,或导出 SQL 文件。

  4. 日志收集:可将日志统一输出到 stdout/stderr,配合 Docker 的日志驱动进行收集。也可以挂载日志卷。

  5. 资源限制:在 docker-compose.yml 中为服务设置 mem_limitcpus,防止单个容器占用过多主机资源。


十一、总结与下集预告

今天我们完成了电商项目的 Docker 容器化,彻底告别“环境不一致”的痛苦:

  • 使用 Dockerfile 构建了 Django + uWSGI 镜像,支持多进程并发;

  • 用 docker-compose 编排了 Nginx、Django、Celery、Redis、PostgreSQL 五大服务;

  • 配置了生产级 PostgreSQL 数据库,通过环境变量灵活配置;

  • 编写了 entrypoint 脚本自动完成数据库迁移和初始化;

  • 实现了服务间网络通信、数据持久化卷、日志查看等生产必备功能。

现在,整个电商平台可以在任何安装了 Docker 的服务器上,通过两条命令(git clone + docker-compose up)迅速上线。这是现代部署的标准范式。但我们的部署还差最后一环——HTTPS 安全证书。第 29 篇,我将带大家配置 HTTPS 与域名绑定,让用户通过 https://www.yoursite.com 安全访问,真正达到上线标准。

想了解更多还可以去公众号、今日头条搜索「IT策士」,一起升级 IT 思维 !


*本文为《Django 从 0 到 1 打造完整电商平台》系列第 28 篇。
*

posted @ 2026-05-26 23:32  IT策士  阅读(11)  评论(0)    收藏  举报