资讯动态

Django生产级部署实战:从Docker到PostgreSQL的完整工程链路

发布时间:2026/10/23 5:09:09 来源:尧图企业网站定制
简介这是一本由William S. Vincent撰写的Django进阶实用指南面向已有一定Django基础、希望构建生产级网站的开发者围绕从项目初始化到部署的完整流程系统讲解Docker容器化、PostgreSQL接入、自定义用户模型、邮箱验证登录、测试策略、环境变量与安全性能优化等主题能够帮助读者理解开发玩具应用与正式站点在配置和工程实践上的现实差距。内容兼顾原理说明与可直接落地的配置示例。压缩包为单个PDF电子书大小25.89MB包含完整章节及目录预览可在本地或平板中按章阅读。目前已有61人浏览学习适合后端开发人员、全栈学习者作为进阶参考资料。书中以书店项目为主线逐步搭建生产级工程并给出Docker、PostgreSQL、allauth认证、静态资源管理、自动化测试等关键环节的配置思路与常用做法读者可据此对照自身项目补齐部署、安全和测试短板。1. 生产级Django和本地Demo之间隔着一整条工程链路本地跑通一个Django应用并不难sqlite加migrate加runserver十分钟就能看到页面。但把它变成一个能扛住真实流量、能部署、能迭代的 Django 生产级网站Docker、PostgreSQL、自定义用户模型、测试、环境变量、安全配置这些环节一个都绕不开。问题在于默认的 SQLite 和默认 User 模型在项目初期根本看不出风险等上线前才暴露就是大改。这本书的定位正好卡在这个断层上它不教你写 Hello World而是用一个模拟书店项目从环境搭起一路走到权限、支付、搜索、性能与安全。适合谁写过几个 Django 应用但没完整跑过生产部署正被“本地好好的上线就翻车”折磨的开发者。如果你正准备把一个新项目从零建成可交付的站点这份资源可以拿来当主线照着走。2. 环境先对齐生产Docker和PostgreSQL把项目底座立起来书里把 Docker 放在第一章不是炫技而是为了一个很实际的目标让本地开发环境和生产环境尽量一致。这样在后面部署时意外会少很多。2.1 生产项目为什么不用SQLiteSQLite 是文件型数据库开发演示很顺手但并发写入能力弱数据类型和配置也和 PostgreSQL 有不少差异。生产环境里最常见的数据库选择是 PostgreSQL这也是书里默认的选型。更关键的是Django 项目一旦跑完迁移、产生了业务数据再换数据库就是一次大手术几乎等于推倒重来。我拿到新项目时默认第一件事就是看它的数据库配置是不是默认的 SQLite。如果是我会在写任何业务模型之前先换掉。常见做法是项目第一天就用 PostgreSQL哪怕本地也跑在 Docker 里。这样开发、测试、生产三套环境的数据库行为才一致不会出现“本地好好的线上查出来类型对不上”这种问题。2.2 用Docker Compose把数据库和Web服务一起拉起一个比较顺手的 Docker Compose 配置长这样services: db: image: postgres:16-alpine volumes: - postgres_data:/var/lib/postgresql/data environment: - POSTGRES_USERpostgres - POSTGRES_PASSWORDpostgres - POSTGRES_DBpostgres web: build: . command: python manage.py runserver 0.0.0.0:8000 volumes: - .:/code ports: - 8000:8000 depends_on: - db volumes: postgres_data:这个文件把数据库做成一个独立容器Web 服务依赖它启动。postgres_data是命名卷保证容器重建后数据不丢depends_on控制启动顺序让数据库容器先起来再起 Web。实际工作里我还会加restart: unless-stopped避免服务器重启后容器不自动恢复。参数说明POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DB要保持和 Django 的 settings 里一致端口映射只做在 Web 服务上数据库不要映射到宿主机只在内网里被应用访问。镜像标签建议固定一个版本团队内部统一不要默认latest。2.3 Django接入PostgreSQLsettings与PsycopgDjango 侧要做的改动不多核心是DATABASES配置DATABASES { default: { ENGINE: django.db.backends.postgresql, NAME: postgres, USER: postgres, PASSWORD: postgres, HOST: db, # docker compose 里直接写服务名 PORT: 5432, } }这里最容易出错的是HOST。在 Docker Compose 里web 容器访问数据库不能写localhost要写服务名db。容器网络里每个服务都有独立的主机名服务名就是内部 DNS 域名这是 Docker 的解析规则不是玄学。驱动方面Django 连 PostgreSQL 需要psycopg这个库我一般直接用psycopg[binary]避免本机编译。不装驱动的话执行migrate时会在连接阶段报错提示找不到数据库适配器。顺序上别乱先docker compose up -d拉起数据库再执行migrate。2.4 分离模式与容器内的日常操作容器启动后日常操作基本都是这几条命令docker compose up -d # 后台运行所有服务 docker compose ps # 查看状态 docker compose logs -f db # 跟踪数据库日志 docker compose exec web python manage.py migrate # 在 web 容器里执行迁移分离模式-d让容器在后台跑不占住终端。日志是排错第一入口——数据库连不上时logs里通常直接能看到 connection refused。我一般会把“启动”和“迁移”拆成两步做因为数据库容器起来后还需要几秒准备立刻迁移可能撞上“数据库还没就绪”的假故障。2.5 容器里migrate连不上数据库这个坑太常见了。现象是docker compose up正常但migrate报连接拒绝原因绝大多数是 settings 里写了localhost或者没等数据库就绪解决方法是把HOST改成服务名如果还不行就加一个等待脚本或者手动重试。这类问题在新手阶段几乎人人都会遇到一次关键是知道它在哪个环节。3. 用户系统一步到位自定义User模型与完整认证流程模拟书店项目建立时书里特意把自定义用户模型放在业务应用之前这一步在我自己做过几个项目后深有体会几乎所有生产项目最后都会给用户加字段越晚改越痛苦。3.1 为什么项目第一天就要换掉默认UserDjango 官方文档对自定义 User 的建议是新项目开始时就用如果在生产数据上改会触发一连串外键引用变更迁移风险极大。原因在于 Django 自带的auth_user表已经定义了 username、password 等字段替换模型等于把用户表的引用全部重指。自定义的方法很直接继承AbstractUser加上自己的业务字段。但这个决定必须在第一次migrate之前做好。已经跑过迁移的库再改就要处理历史数据和权限关联费时费力。提示新建项目时先设置AUTH_USER_MODEL再执行迁移否则后面会很痛苦。3.2 自定义User模型与配套表单、Admin先写模型from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): age models.PositiveIntegerField(nullTrue, blankTrue) def __str__(self): return self.email or self.usernameAbstractUser保留了 Django 自带的用户名、密码和权限体系只需要追加业务字段。然后把模型注册到 settingsAUTH_USER_MODEL users.CustomUser这一步做完项目里所有用户外键都指向新模型。但光改模型不够配套的表单和 Admin 也要同步换否则注册页和后台都会报错。from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .forms import CustomUserCreationForm, CustomUserChangeForm from .models import CustomUser class CustomUserAdmin(UserAdmin): add_form CustomUserCreationForm form CustomUserChangeForm model CustomUser list_display [email, username] admin.site.register(CustomUser, CustomUserAdmin)这里有个细节CustomUserAdmin必须继承UserAdmin否则新加的字段在后台里不会显示。表单也要继承 Django 自带的UserCreationForm和UserChangeForm显式指定model CustomUser。3.3 登录、登出、注册的URL与视图Django 自带的认证视图足够覆盖大多数场景不需要从零写登录逻辑from django.contrib.auth.views import LoginView, LogoutView from django.urls import path from .views import SignUpView urlpatterns [ path(login/, LoginView.as_view(template_nameregistration/login.html), namelogin), path(logout/, LogoutView.as_view(), namelogout), path(signup/, SignUpView.as_view(), namesignup), ]LoginView和LogoutView是 Django 内置的类视图模板默认放在registration/login.html。SignUpView则是一个自定义的CreateView表单用前面写的CustomUserCreationForm。重定向行为通过 settings 里的LOGIN_REDIRECT_URL控制注册成功后跳到首页还是个人中心取决于业务需求。3.4 邮箱验证用allauth补完整注册链路默认注册流程只有用户名和密码但生产环境通常要求邮箱验证。django-allauth是这里最常用的选择它覆盖登录、登出、注册、邮箱验证和社交登录。关键配置AUTHENTICATION_BACKENDS [ django.contrib.auth.backends.ModelBackend, allauth.account.auth_backends.AuthenticationBackend, ] EMAIL_BACKEND django.core.mail.backends.console.EmailBackend ACCOUNT_LOGOUT_REDIRECT /EMAIL_BACKEND设为 console 时邮件不会真发出去而是打印在控制台方便开发阶段查看验证链接。上线前换成 SMTP 后端把邮箱账号、密码、服务地址这些敏感信息放进环境变量。如果业务想做“仅邮箱登录”可以设置ACCOUNT_AUTHENTICATION_METHOD email和ACCOUNT_USERNAME_REQUIRED False但前提是邮件服务必须可用否则用户收不到验证邮件入口就断了。3.5 用测试把认证流程钉死认证流程是用户系统的核心必须有测试兜底from django.test import TestCase from django.urls import reverse from .models import CustomUser class SignupTests(TestCase): classmethod def setUpTestData(cls): cls.user CustomUser.objects.create_user( usernametestuser, password12345 ) def test_login_page(self): response self.client.get(reverse(login)) self.assertEqual(response.status_code, 200) self.assertContains(response, 登录)setUpTestData是类方法测试类里所有用例共享这一份数据比setUp里重复建对象快。测试的重点三件事登录页能打开、注册后用户被正确创建、访问受保护页面会跳转登录。这些用例不是应付事后面改认证逻辑、动 User 模型时能第一时间拦住回归。4. 业务模块从0到1图书、评论与文件上传的完整写法模拟书店项目把业务拆成了图书、评论、文件上传三条线跟着走一遍基本就能摸清 Django 生产项目的模块套路。4.1 图书应用模型与Admin的最小闭环图书模型的核心也就是标题、作者、价格这些字段from django.db import models class Book(models.Model): title models.CharField(max_length200) author models.CharField(max_length200) price models.DecimalField(max_digits5, decimal_places2) def __str__(self): return self.title价格用DecimalField而不是FloatField因为浮点数在价格计算里会出现精度误差这个从第一天就别将就。max_digits5表示最多五位数字decimal_places2表示保留两位小数合起来能存的最大值是 999.99对大多数图书定价够用。模型定义好后在 admin 里注册配合 Media 加上封面就成了可维护的基础 CRUD 模块。4.2 列表页和详情页context_object_name与get_absolute_urlDjango 的通用视图能让页面代码量压缩很多from django.views.generic import ListView, DetailView from .models import Book class BookListView(ListView): model Book template_name books/book_list.html context_object_name books class BookDetailView(DetailView): model Book template_name books/book_detail.html context_object_name bookListView默认传给模板的变量是object_list但模板里用context_object_name指定为books更直观语义也更清楚。DetailView默认用主键作为 URL 参数。如果不想对外暴露数字 ID可以换成 slug 或 UUID书里专门讨论过这三者的取舍结论很实际对外分享的链接页面用 slug内部接口用数字主键就够。模型上最好补一个get_absolute_urlfrom django.urls import reverse def get_absolute_url(self): return reverse(book_detail, args[str(self.pk)])这样在模板和 admin 里都能一键跳转详情页不用到处拼 URL。4.3 评论应用外键、模型与模板渲染评论和图书是典型的一对多关系class Review(models.Model): book models.ForeignKey(Book, on_deletemodels.CASCADE, related_namereviews) author models.CharField(max_length200) body models.TextField() created models.DateTimeField(auto_now_addTrue) def __str__(self): return f{self.author} -- {self.book.title}on_deletemodels.CASCADE表示图书删除时评论一起删避免孤儿数据。related_namereviews让模板里可以直接用book.reviews.all()拿到全部评论语义比默认的review_set清楚得多。模板渲染时要判断空列表没有评论时显示“暂无评论”不要留白页面。这里还藏着一个性能问题列表页循环显示图书和评论时如果不对查询做优化每条评论都会触发一次 SQL这个等到后面性能章节再展开。4.4 文件与图片上传从ImageField到Media配置上传功能涉及模型字段和静态服务两个层面。模型侧class Book(models.Model): # ... cover models.ImageField(upload_tocovers/)ImageField依赖 Pillow 库字段存的是文件路径模板里用{{ book.cover.url }}拿访问链接。配套的 settings 配置MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / mediaMEDIA_ROOT是文件实际落盘的位置MEDIA_URL是浏览器访问的 URL 前缀。开发环境下要在主urls.py里加 static serve生产环境则交给 Web 服务器或对象存储处理。注意光靠ImageField拦不住恶意文件后端要做后缀名和文件头的白名单校验。模板里展示封面img src{{ book.cover.url }} alt{{ book.title }}4.5 测试模板与HTML结构页面结构测试是生产项目里很容易被跳过的一环但它对改版特别有价值from django.test import TestCase from django.urls import reverse from .models import Book class BookPageTests(TestCase): classmethod def setUpTestData(cls): cls.book Book.objects.create( titleDjango实战, author某开发者, price39.9 ) def test_book_list_page(self): response self.client.get(reverse(book_list)) self.assertTemplateUsed(response, books/book_list.html) self.assertContains(response, Django实战)assertTemplateUsed确认视图渲染的是预期模板assertContains确认页面里出现了预期内容。我会把页面里的关键文字、按钮、链接都写成断言防止以后改版把入口弄丢。这本质上不是在测 Django而是在给页面结构留一张契约。5. 生产功能避坑搜索、权限、支付与查询性能的排查记录这一章集中记录我在复现和扩展这个模拟项目时踩过的问题每一条都对应着生产级网站的高频故障点。都是血泪经验提前写在这里能省不少排查时间。5.1 搜索多个关键词一起查时结果越来越少现象搜索表单里输入两个关键词返回结果反而少于只输入一个词。原因直接在get_queryset里连续加两个filterDjango 默认用AND连接。比如先筛title再筛author结果要求一本书同时满足两个条件结果集自然变小。新手很容易被“多一个条件就多一次筛选”的直觉带偏。解决用Q对象显式组合OR条件from django.db.models import Q def get_queryset(self): q self.request.GET.get(q, ) return Book.objects.filter( Q(title__icontainsq) | Q(author__icontainsq) )icontains生成不区分大小写的 LIKE 查询Q(title__icontainsq) | Q(author__icontainsq)表示“标题包含或作者包含”。如果要做多关键词拆词搜索通常用循环把Q对象叠加而不是机械地加filter。5.2 权限要求登录的视图未登录用户直接看到404现象给视图加了LoginRequiredMixin未登录用户访问却返回 404 而不是跳转登录页。原因权限 Mixin 有两个职责——未登录时重定向已登录但不满足权限时返回 403。出现 404 一般是 Mixin 写在了类定义的最右边或者get_queryset把对象过滤掉了导致DetailView查不到对象。解决把权限 Mixin 放在继承列表最左侧class BookDetailView(LoginRequiredMixin, DetailView): model BookMixin 的顺序决定方法解析顺序LoginRequiredMixin必须在最前面确保视图分发前先做拦截。更细的自定义权限需求可以接着看PermissionRequiredMixin和UserPassesTestMixin用户组权限是生产环境里控制后台角色的常用手段。5.3 支付测试密钥进了生产配置现象测试环境一切正常上线后支付接口反复报凭证无效。原因支付服务商提供的测试密钥被硬编码在 settings 里生产环境加载的也是同一套测试凭证。这种问题最怕的是线上环境还开着DEBUGTrue一旦报错直接把敏感信息打到页面上。解决密钥全部走环境变量按环境注入import os PAYMENT_PUBLISHABLE_KEY os.environ.get(PAYMENT_PUBLISHABLE_KEY, ) PAYMENT_SECRET_KEY os.environ.get(PAYMENT_SECRET_KEY, )任何密钥都不要写进提交到版本库的文件。.env文件不入库线上密钥通过部署平台的环境变量配置。支付流程还要考虑订单状态和支付回调的一致性这本书的处理方式很稳先建订单应用再接支付服务商接口最后用权限类保护订单详情页每一步都有对应测试。5.4 性能列表页加载慢一个Book触发几十次查询现象书籍列表页打开很慢数据库查询次数暴增。原因模板里循环读取book.reviews.all()每个反向关系都独立执行一次 SQL这是最常见的 N1 查询问题。解决在视图里用prefetch_related提前取回关联数据def get_queryset(self): return Book.objects.prefetch_related(reviews)select_related适用于单值外键prefetch_related适用于多值反向关系。列表页在get_queryset里做这一步模板里再调用book.reviews.all()就不会产生额外 SQL。生产上一旦页面查询次数明显超过页面元素的数量优先怀疑 N1。5.5 静态文件collectstatic之后样式全丢现象本地样式正常部署后 CSS 全部 404。原因STATIC_ROOT没配置或者压根没执行collectstatic。开发环境里 Django 直接 serve 静态文件生产环境要求把所有 App 的静态资源收集到同一个目录再由 Web 服务器统一提供。解决STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static] STATIC_ROOT BASE_DIR / staticfilesSTATICFILES_DIRS是开发时的源文件目录STATIC_ROOT是collectstatic的输出目录。部署时执行python manage.py collectstatic --noinput然后检查输出目录里有没有 CSS 和 JS。前端样式库这类第三方资源开发阶段用 CDN 没问题生产环境考虑到内网访问和版本锁定建议下载到本地静态目录。6. 上线前最后一遍过用检查清单和调试工具守住最后一次提交项目写完只是开始上线前那一步才是生产级网站和 Demo 的分水岭。我习惯在最后一次提交前强制走一遍下面的检查路径基本能拦下大部分部署事故。先跑三个命令python manage.py check --deploy python manage.py makemigrations --check --dry-run python manage.py collectstatic --noinputcheck --deploy是 Django 内置的安全检查会把与部署相关的配置风险列出来比如DEBUGTrue、ALLOWED_HOSTS为空这类问题。makemigrations --check --dry-run确保没有任何未提交的模型变更避免线上迁移和代码对不上。collectstatic --noinput把静态文件收集到STATIC_ROOT做完这步再检查输出目录。然后是本地模拟生产验证把DEBUG设为False在 settings 里给ALLOWED_HOSTS配上你的域名或服务器 IP再跑runserver。这时候如果页面样式全丢、admin 后台打不开说明静态文件配置还有问题。之后把安全相关的几项过一遍SESSION_COOKIE_SECURE、CSRF_COOKIE_SECURE都打开确保仅走 HTTPS。最后用django-debug-toolbar做一次页面体检。这个工具只在DEBUGTrue和INTERNAL_IPS本地访问时启用打开列表页和详情页观察右侧面板的 SQL 次数。如果发现某页面查询次数远超预期回去把prefetch_related补上。缓存配置可以先用本地内存缓存跑通再换 Redis 这类生产级后端。从那以后我每次上线前都强制走一遍先跑check --deploy再makemigrations --check然后collectstatic最后开着 debug-toolbar 把核心页面刷一遍。这套流程用顺手后几乎没有再出现上线后才发现配置问题的翻车现场。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑