深入浅出:使用 Gunicorn + Nginx + Docker 将 Django 项目部署到云服务器

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。

在本地跑得好好的 Django,一上服务器就各种报错?

环境不一致、依赖缺失、进程守护......传统部署的坑数不胜数。

本文将带你用 Gunicorn + Nginx + Docker 三件套,把 Django 项目丝滑地送上云服务器。

全程手摸手,有丰富的控制台输出与可复现的例子,新手能看懂,进阶者也能收获最佳实践。


1. 先弄明白:这三者分别是什么?

  • Gunicorn :一个 WSGI HTTP 服务器,专为运行 Python Web 应用而生。相比 Django 自带的 runserver,它支持多进程、更稳定,是生产环境的标配。

  • Nginx:高性能的反向代理和静态文件服务器。在这里它负责接收外界请求,动态内容转发给 Gunicorn,静态文件直接由自己返回,极大提升效率。

  • Docker:将应用及其依赖打包成容器,消除"我这能跑,你那不行"的环境差异问题。结合 Docker Compose 可以轻松编排多服务(Web + Nginx + 数据库等)。

整体数据流:

用户浏览器 -> 云服务器 80/443 端口 -> Nginx 容器 -> (动态请求) -> Gunicorn 容器 -> Django 应用


2. 预备一个示例 Django 项目

假设我们的项目名为 myblog,结构如下:

bash 复制代码
myblog/
├── manage.py
├── myblog/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── blog/                    # 一个简单应用
│   ├── views.py
│   └── urls.py
├── requirements.txt
└── Dockerfile

快速创建一个最简版本(实际操作时你可以用自己的项目):

bash 复制代码
django-admin startproject myblog
cd myblog
python manage.py startapp blog

blog/views.py 中加入一个简单视图:

bash 复制代码
from django.http import HttpResponse

def home(request):
    return HttpResponse("Hello, Django & Docker!")

myblog/urls.py 中注册:

bash 复制代码
from django.contrib import admin
from django.urls import path
from blog.views import home

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home, name='home'),
]

修改 settings.py,允许外部访问(部署时务必改为具体域名或 IP):

bash 复制代码
ALLOWED_HOSTS = ['*']   # 仅测试用,生产环境请指定真实域名
STATIC_ROOT = BASE_DIR / 'staticfiles'   # 收集静态文件的目录

生成依赖文件:

bash 复制代码
pip freeze > requirements.txt

requirements.txt 内容示例:

bash 复制代码
Django==4.2
gunicorn==21.2.0

3. 手写 Dockerfile:用 Gunicorn 启动 Django

在项目根目录创建 Dockerfile

bash 复制代码
# 使用官方 Python 镜像(slim 版本更小)
FROM python:3.11-slim

# 设置环境变量,避免 Python 生成 .pyc 文件,并开启标准输出
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# 设置工作目录
WORKDIR /app

# 安装系统依赖(如果连接数据库可能需要)
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc libpq-dev && \
    rm -rf /var/lib/apt/lists/*

# 复制并安装 Python 依赖
COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt

# 复制项目代码
COPY . .

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

# 暴露 8000 端口(Gunicorn 默认监听)
EXPOSE 8000

# 启动命令:使用 Gunicorn 运行 WSGI 应用
CMD ["gunicorn", "myblog.wsgi:application", "--bind", "0.0.0.0:8000"]

构建镜像,你能看到类似下面的输出:

bash 复制代码
$ docker build -t myblog:latest .
Sending build context to Docker daemon  45.06kB
Step 1/10 : FROM python:3.11-slim
 ---> a1b2c3d4e5f6
Step 2/10 : ENV PYTHONDONTWRITEBYTECODE=1
 ---> Running in 12ab34cd56ef
 ---> 7890abcd1234
...
Step 10/10 : CMD ["gunicorn", "myblog.wsgi:application", "--bind", "0.0.0.0:8000"]
 ---> Running in ef567890abcd
 ---> fedc09876543
Successfully built fedc09876543
Successfully tagged myblog:latest

跑起来试试:

bash 复制代码
$ docker run -d -p 8000:8000 --name myblog-test myblog:latest

查看容器日志,熟悉的 Gunicorn 启动信息:

bash 复制代码
$ docker logs myblog-test
[2026-05-17 14:23:45 +0000] [1] [INFO] Starting gunicorn 21.2.0
[2026-05-17 14:23:45 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
[2026-05-17 14:23:45 +0000] [1] [INFO] Using worker: sync
[2026-05-17 14:23:45 +0000] [8] [INFO] Booting worker with pid: 8

浏览器访问 http://localhost:8000,就能看到 Hello, Django & Docker!

(停止并删除测试容器:docker rm -f myblog-test

进阶提示 :可以通过 --workers 参数调整进程数,或通过环境变量 WEB_CONCURRENCY 动态设置,后面会提到。


4. 引入 Nginx:更贴近生产环境的编排

生产环境中,我们不会把 Gunicorn 直接暴露给用户,而是前面再放一个 Nginx。

这里使用 Docker Compose 定义两个服务,并用共享卷处理静态文件。

4.1 Nginx 配置文件

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

bash 复制代码
upstream myblog_app {
    # web 是 docker-compose 中 Django 服务的名称
    server web:8000;
}

server {
    listen 80;
    server_name _;   # 用真实域名时替换

    # 静态文件直接由 Nginx 提供
    location /static/ {
        alias /staticfiles/;
    }

    # 动态请求转发给 Gunicorn
    location / {
        proxy_pass http://myblog_app;
        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;
    }
}

4.2 Docker Compose 编排文件

在项目根目录创建 docker-compose.yml

bash 复制代码
version: '3.8'

services:
  web:
    build: .
    # 不直接暴露端口,仅内部使用
    expose:
      - "8000"
    volumes:
      - static_volume:/app/staticfiles   # 共享静态文件卷
    environment:
      - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY:-change-me}
      - DEBUG=False
    command: gunicorn myblog.wsgi:application --bind 0.0.0.0:8000 --workers 3
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - static_volume:/staticfiles:ro   # 只读方式挂载静态文件卷
    depends_on:
      - web
    restart: unless-stopped

volumes:
  static_volume:

新手理解

  • static_volume 是一个命名卷,web 服务运行 collectstatic 后静态文件就存入其中。

  • Nginx 挂载同一个卷,直接向用户提供 /static/ 下的文件,完全不经过 Django,速度飞快。

4.3 启动编排,观察日志

bash 复制代码
$ docker-compose up -d
Creating network "myblog_default" with the default driver
Creating volume "myblog_static_volume" with default driver
Creating myblog_web_1 ... done
Creating myblog_nginx_1 ... done

查看所有容器的状态:

bash 复制代码
$ docker-compose ps
     Name                   Command               State                  Ports
----------------------------------------------------------------------------------------------
myblog_nginx_1   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:80->80/tcp
myblog_web_1     gunicorn myblog.wsgi:applic ...   Up      8000/tcp

看一下日志,确认两个服务都正常:

bash 复制代码
$ docker-compose logs -f
# web 输出:
web_1    | [2026-05-17 14:30:01 +0000] [1] [INFO] Starting gunicorn 21.2.0
web_1    | [2026-05-17 14:30:01 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
web_1    | [2026-05-17 14:30:01 +0000] [8] [INFO] Booting worker with pid: 8
web_1    | [2026-05-17 14:30:01 +0000] [9] [INFO] Booting worker with pid: 9
web_1    | [2026-05-17 14:30:01 +0000] [10] [INFO] Booting worker with pid: 10
# nginx 输出:
nginx_1  | /docker-entrypoint.sh: Configuration complete; ready for start up

访问 http://localhost(Nginx 的 80 端口),同样得到 Hello, Django & Docker!

静态文件测试:访问 http://localhost/static/admin/css/base.css 能看到 Django admin 的样式,证明 Nginx 直接返回了静态文件。


5. 部署到真实的云服务器

假设你有一台 Ubuntu 22.04 的云服务器(阿里云、腾讯云、AWS 均可),并已通过 SSH 登录。

5.1 服务器环境准备

bash 复制代码
# 更新包索引
sudo apt update
# 安装 Docker
curl -fsSL https://get.docker.com | sudo sh
# 启动 Docker 并设置开机自启
sudo systemctl enable docker --now
# 安装 Docker Compose(独立插件方式)
sudo apt install docker-compose-plugin -y
# 验证
docker --version
docker compose version

控制台输出示例:

bash 复制代码
$ docker --version
Docker version 24.0.7, build afdd53b
$ docker compose version
Docker Compose version v2.21.0

5.2 上传项目代码

推荐使用 Git 管理代码:

bash 复制代码
# 在服务器上克隆项目(以 GitHub 为例)
cd /opt
git clone https://github.com/yourname/myblog.git
cd myblog

5.3 配置环境变量(重要)

创建 .env 文件存储敏感信息,生产环境务必修改:

bash 复制代码
DJANGO_SECRET_KEY=你的超级长随机字符串
DEBUG=False
ALLOWED_HOSTS=你的服务器IP或域名

并在 docker-compose.yml 中传递给 web 服务,同时修改 Gunicorn 命令引用环境变量:

bash 复制代码
web:
  ...
  environment:
    - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
    - DEBUG=${DEBUG:-False}
    - ALLOWED_HOSTS=${ALLOWED_HOSTS}
  command: >
    gunicorn myblog.wsgi:application
    --bind 0.0.0.0:8000
    --workers ${WEB_CONCURRENCY:-3}

对应的 settings.py 中读取这些变量:

bash 复制代码
import os
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'fallback-key')
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '*').split(',')

5.4 在服务器上启动应用

bash 复制代码
$ docker compose up -d --build
[+] Building 23.4s (12/12) FINISHED
 => [internal] load build definition from Dockerfile               0.0s
 => => transferring dockerfile: 348B                               0.0s
 ...
[+] Running 3/3
 ✔ Network myblog_default          Created                         0.1s
 ✔ Container myblog-web-1          Started                         1.2s
 ✔ Container myblog-nginx-1        Started                         1.5s

检查运行状态:

bash 复制代码
$ docker compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
myblog-nginx-1      "/docker-entrypoint...."   nginx               running             0.0.0.0:80->80/tcp
myblog-web-1        "gunicorn myblog.wsgi..."  web                 running             8000/tcp

现在,在 云服务器的安全组 中开放 80 端口,用浏览器访问服务器公网 IP,就能看到你的 Django 应用了!


6. 进阶实战:加入 PostgreSQL 数据库

SQLite 不适合生产,我们再加一个数据库服务,完整演示多容器编排。

修改 docker-compose.yml,增加 db 服务:

bash 复制代码
services:
  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      - POSTGRES_DB=${DB_NAME:-myblog}
      - POSTGRES_USER=${DB_USER:-mybloguser}
      - POSTGRES_PASSWORD=${DB_PASSWORD:-securepassword}
    restart: unless-stopped

  web:
    build: .
    expose:
      - "8000"
    volumes:
      - static_volume:/app/staticfiles
    environment:
      - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
      - DEBUG=False
      - DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
    depends_on:
      - db
    command: >
      gunicorn myblog.wsgi:application
      --bind 0.0.0.0:8000
      --workers 3
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - static_volume:/staticfiles:ro
    depends_on:
      - web
    restart: unless-stopped

volumes:
  postgres_data:
  static_volume:

.env 中增加:

bash 复制代码
DB_NAME=myblog
DB_USER=mybloguser
DB_PASSWORD=你的复杂密码

在 Django 的 settings.py 使用 dj-database-url 解析数据库连接(需加入 requirements.txt):

bash 复制代码
import dj_database_url
DATABASES = {
    'default': dj_database_url.config(
        default=os.environ.get('DATABASE_URL', 'sqlite:///db.sqlite3')
    )
}

重新构建并启动:

bash 复制代码
$ docker compose up -d --build
Creating myblog_db_1 ... done
Creating myblog_web_1 ... done
Creating myblog_nginx_1 ... done

查看数据库容器日志:

bash 复制代码
$ docker compose logs db
db_1  | 2026-05-17 14:45:00.123 UTC [1] LOG:  database system is ready to accept connections

执行数据库迁移:

bash 复制代码
$ docker compose exec web python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  ...

完美!现在你的 Django 应用已经连接上了生产级 PostgreSQL。


7. 更进一步:HTTPS、CI/CD 等最佳实践(简览)

7.1 开启 HTTPS

可以在 Nginx 容器内配置 SSL,最简单的方法是使用 certbot 及其 Nginx 插件,但容器化环境下推荐使用 Certbot 官方镜像Traefik。这里给出一个手动整合 Certbot 的思路:

  • 先以 HTTP 启动,通过 certbot 获取证书,映射到宿主机的证书目录。

  • 修改 Nginx 配置监听 443,证书路径指向挂载的文件。

  • 使用 docker compose exec nginx nginx -s reload 热重载。

关键 Nginx SSL 配置片段

bash 复制代码
server {
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    ...
}

7.2 使用 CI/CD 自动部署

例如 GitHub Actions 工作流,在推送代码后自动构建镜像并部署到服务器:

bash 复制代码
- name: Deploy to Server
  uses: appleboy/ssh-action@v1.0
  with:
    host: ${{ secrets.SERVER_HOST }}
    username: ${{ secrets.SERVER_USER }}
    key: ${{ secrets.SSH_PRIVATE_KEY }}
    script: |
      cd /opt/myblog
      git pull
      docker compose up -d --build

7.3 性能与监控

  • Worker 数量 :建议 (2 * CPU核心数) + 1,可通过 WEB_CONCURRENCY 环境变量调整。

  • 日志docker compose logs -f 实时查看,生产环境建议接入 ELK 或 Loki。

  • 健康检查 :在 docker-compose.yml 中为 web 服务添加 healthcheck,配合 depends_on 条件使用 condition: service_healthy


8. 常见问题快速排查


9. 总结

通过这篇长文,我们从零开始,经历了:

  • 创建一个简单的 Django 项目

  • 用 Dockerfile 将其容器化,并用 Gunicorn 启动

  • 用 Docker Compose 编排 Nginx + Gunicorn,处理静态文件

  • 部署到云服务器并连接 PostgreSQL

  • 探索了 HTTPS、CI/CD 等进阶方向

Gunicorn + Nginx + Docker 这套组合已经成为 Django 生产部署的事实标准。它隔离环境、简化运维、易于扩展,无论是个人项目还是企业级应用都能游刃有余。

希望这篇文章能帮你跨过从开发到上线的鸿沟,享受流畅的部署体验。

相关推荐
jran-1 小时前
Docker 数据卷&应用部署
运维·docker·容器
jran-1 小时前
Docker dockerfile镜像制作&compose服务编排&私有仓库
java·docker·容器
悠然南风2 小时前
Nginx 学习总结-补充
nginx
CCPC不拿奖不改名2 小时前
PostgreSQL数据库部署linux服务器流程
linux·服务器·数据库·windows·python·docker·postgresql
cgsthtm2 小时前
openEuler release 24.03 (LTS-SP2) 安装 docker
docker·systemctl·dnf·openeuler 24.03
古城小栈2 小时前
K8s 核心知识 讲解
docker·容器·kubernetes
杨云龙UP2 小时前
MySQL主库高峰期备份引发504故障:从库手动切换接管 + 主从恢复同步 + Docker版DB2重启实战_2026-05-17
linux·运维·数据库·mysql·docker·容器·centos
My_Java_Life3 小时前
windows中使用docker部署Milvus和Autt
windows·docker·milvus
jran-3 小时前
Docker 架构&命令
运维·docker·容器