Bitpoll 0.11.0 Compose 部署实战
Bitpoll 0.11.0 三服务 Compose 编排部署全流程:官方基线补数据库与健康检查、settings 与 nginx 反代配置、启动/登录/投票全链路实测回显、持久化验证与五则排障实录。
Bitpoll 0.11.0 Compose 部署实战
目标:在一台 Linux 服务器上,用 Compose 把 Bitpoll 0.11.0 以「数据库 + 应用 + 反向代理」三服务形态完整拉起,完成配置初始化、超级管理员创建、投票创建与访问的全链路验证。全部命令与回显均为 2026-11-01 在 Ubuntu 22.04 服务器实测原文照抄(凭据按本文示例值书写)。项目背景与选型论证见《Bitpoll 0.11.0 介绍与选型指南》。
一、环境信息
| 项 | 值 |
|---|---|
| 操作系统 | Ubuntu 22.04(x86_64) |
| 软件版本 | Bitpoll 0.11.0(官方最新稳定版,2026-05-06 发布,来源 GitHub releases.atom,查询日期 2026-11-01) |
| 编排工具 | Docker Compose v2(docker compose 子命令形态) |
| 对外 Web 端口 | 8136(ss -ltn 实测空闲后取用) |
| 应用调试端口 | 8137 → 服务内部 3009(仅排障用,生产可关) |
| 自定义网络 | bitpoll-net,显式子网 10.89.57.0/24 |
| 数据库 | PostgreSQL 17(独立服务,bind mount 数据目录 ./data/postgres) |
| 应用服务 | 官方发行产物 ghcr.io/fsinfuhh/bitpoll:latest(0.11.0 对应发布线) |
| 反向代理 | nginx 1.23,uwsgi 协议转发到应用服务 3008 端口 |
| 管理员账号 | admin / q1w2e3r4(示例值,密码务必替换) |
架构要点:Bitpoll 应用进程通过 uWSGI 暴露 uwsgi 协议端口 3008(生产路径)与 HTTP 端口 3009(开发路径)。对外只应暴露 nginx 的 80(映射 8136);3009 映射到 8137 仅为部署期验证,稳定后建议从编排文件删除。
二、官方基线与必要适配
官方仓库根目录提供 compose.yaml,内容仅有两个服务:
services:
bitpoll:
build:
dockerfile: Dockerfile
context: .
ports:
- 3008:3008
- 3009:3009
volumes:
- ./run/static:/opt/static
- ./run/config:/opt/config
- ./run/log/:/opt/log
web:
image: nginx:1.23
volumes:
- ./docker_files/nginx/nginx.conf:/etc/nginx/conf.d/default.conf
- ./run/static:/opt/static
ports:
- "80:80"
直接照抄这份基线跑不起来,原因与适配动作:
- 没有数据库服务:官方假设数据库外部提供。本文补一个
postgres:17-alpine服务,并配pg_isready健康检查 +depends_on.condition: service_healthy,避免应用先于数据库就绪而崩溃重启。 - 没有固定项目名:补
name: bitpoll-0.11.0-stack,防止与其他项目的同名目录互相误停。 - 端口写死 80:与服务器占用冲突,改映射 8136;3009 改 8137。
- 默认网络无子网规划:显式指定
10.89.57.0/24(先docker network ls核对 10.89 段已占槽位后取空闲)。 - 镜像 tag 用 latest 不可控:本文沿用
ghcr.io/fsinfuhh/bitpoll:latest与 0.11.0 发布线对应(官方镜像由 master 自动构建),升级时以重新拉取 + 数据备份为前提。
三、目录规划
部署根目录 /opt/apps/bitpoll(实测目录为工作目录下 stack/,结构一致):
stack/
├── compose.yaml # 编排文件(见下)
├── keys.env # 密钥类环境变量(SECRET_KEY 等,600 权限)
├── nginx/
│ └── bitpoll.conf # 反代配置
├── run/
│ ├── config/
│ │ └── settings.py # Django 本地配置(挂载进应用 /opt/config/settings.py)
│ ├── static/ # 静态资源目录(应用启动时自动填充,nginx 共享读取)
│ └── log/ # 应用日志
└── data/
└── postgres/ # 数据库数据目录(bind mount,升级/重编排不丢数据)
建目录与密钥生成实测:
mkdir -p run/{config,static,log} nginx data/postgres
python3 -c "from django.core.management.utils import get_random_secret_key as k; print('SECRET_KEY='+k())" >> keys.env
openssl rand -base64 32 | sed 's/^/FIELD_ENCRYPTION_KEY=/' >> keys.env
SECRET_KEY 是 Django 会话签名根密钥,FIELD_ENCRYPTION_KEY 用于加密存储的敏感字段(如受邀者邮箱),两者泄露等同于数据泄露,keys.env 与 run/config/settings.py 都要控制读权限。
四、配置文件
4.1 run/config/settings.py
应用启动时把 /opt/config/settings.py 软链为 Django 的 settings_local.py 覆盖默认配置。最小可用配置(实测生效原文):
# Bitpoll local settings (mounted to /opt/config/settings.py)
import os
SECRET_KEY = '8Se#-M#uT_IDttoPOqcIHi#yryr7bhPeWe-qcBC3^g7Mw_YFJC'
FIELD_ENCRYPTION_KEY = 'l4T6gZ3GO33bLuQNAhsNzrMlF+D8OVP5Aw2C3ftYL0s='
DEBUG = False
ALLOWED_HOSTS = ['*']
SITE_NAME = 'Bitpoll'
REGISTER_ENABLED = True
BASE_URL = 'http://127.0.0.1:8136'
TIME_ZONE = 'Asia/Shanghai'
LANGUAGE_CODE = 'en-us'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'bitpoll',
'USER': 'bitpoll',
'PASSWORD': 'bitpoll',
'HOST': 'db',
'PORT': '5432',
}
}
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DEFAULT_FROM_EMAIL = 'bitpoll@quzuo.net'
PIPELINE_LOCAL = {}
INSTALLED_APPS_LOCAL = []
PUBLIC_POLLS = True
要点:HOST 必须写数据库服务名 db(Compose 网络内 DNS);ALLOWED_HOSTS 至少包含对外域名或本机地址,否则直接 400;生产建议收紧为白名单。配置面逐项详解见本专栏《Bitpoll 0.11.0 配置参数详解》。
4.2 nginx/bitpoll.conf
与官方 docker_files/nginx/nginx.conf 一致(实测原文):
server {
listen 80;
server_name default_server;
root /opt/static;
location / {
include uwsgi_params;
uwsgi_pass bitpoll:3008;
}
location /static {
expires 14d;
alias /opt/static;
}
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}
uwsgi_pass bitpoll:3008 指向应用服务的 uwsgi 端口;/static 直接由 nginx 从共享目录 run/static 吐出,不经应用进程。
4.3 compose.yaml(最终版)
name: bitpoll-0.11.0-stack
services:
db:
image: postgres:17-alpine
container_name: bitpoll-0.11.0-db
restart: unless-stopped
environment:
POSTGRES_DB: bitpoll
POSTGRES_USER: bitpoll
POSTGRES_PASSWORD: bitpoll
volumes:
- ./data/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bitpoll -d bitpoll"]
interval: 5s
timeout: 3s
retries: 12
networks:
- bitpoll-net
bitpoll:
image: ghcr.io/fsinfuhh/bitpoll:latest
container_name: bitpoll-0.11.0-app
restart: unless-stopped
volumes:
- ./run/static:/opt/static
- ./run/config:/opt/config
- ./run/log:/opt/log
ports:
- "8137:3009"
depends_on:
db:
condition: service_healthy
networks:
- bitpoll-net
web:
image: nginx:1.23
container_name: bitpoll-0.11.0-web
restart: unless-stopped
volumes:
- ./nginx/bitpoll.conf:/etc/nginx/conf.d/default.conf
- ./run/static:/opt/static
ports:
- "8136:80"
depends_on:
- bitpoll
networks:
- bitpoll-net
networks:
bitpoll-net:
ipam:
config:
- subnet: 10.89.57.0/24
五、启动全流程与实测回显
5.1 端口占用自查
ss -ltn | grep -E '813[0-9]'
实测回显(本机 8132/8134/8135/8137 已被其他服务占用,8136 空闲,故 Web 取 8136):
LISTEN 0 4096 0.0.0.0:8132 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:8134 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:8135 0.0.0.0:*
5.2 拉起
docker compose up -d
实测回显(数据库健康检查通过后应用才启动,Waiting → Healthy 两行即 depends_on 生效的证据):
Network bitpoll-0110-stack_bitpoll-net Creating
Network bitpoll-0110-stack_bitpoll-net Created
Container bitpoll-0.11.0-db Creating
Container bitpoll-0.11.0-db Created
Container bitpoll-0.11.0-app Creating
Container bitpoll-0.11.0-app Created
Container bitpoll-0.11.0-web Creating
Container bitpoll-0.11.0-web Created
Container bitpoll-0.11.0-db Starting
Container bitpoll-0.11.0-db Started
Container bitpoll-0.11.0-db Waiting
Container bitpoll-0.11.0-db Healthy
Container bitpoll-0.11.0-app Starting
Container bitpoll-0.11.0-app Started
Container bitpoll-0.11.0-web Starting
Container bitpoll-0.11.0-web Started
5.3 状态确认
docker compose ps
实测回显:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
bitpoll-0.11.0-app ghcr.io/fsinfuhh/bitpoll:latest "/bin/sh -c /usr/loc…" bitpoll 6 seconds ago Up Less than a second 3008/tcp, 0.0.0.0:8137->3009/tcp, [::]:8137->3009/tcp
bitpoll-0.11.0-db postgres:17-alpine "docker-entrypoint.s…" db 6 seconds ago Up 5 seconds (healthy) 5432/tcp
bitpoll-0.11.0-web nginx:1.23 "/docker-entrypoint.…" web 6 seconds ago Up Less than a second 0.0.0.0:8136->80/tcp, [::]:8136->80/tcp
5.4 应用启动日志
docker compose logs --tail=12 bitpoll
实测回显(官方入口脚本先执行 manage.py migrate 建表,再发布静态资源,最后拉起 uWSGI):
bitpoll-0.11.0-app | Set PythonHome to /opt/bitpoll/.venv
bitpoll-0.11.0-app | Python main interpreter initialized at 0x788dd2c34390
bitpoll-0.11.0-app | python threads support enabled
bitpoll-0.11.0-app | your server socket listen backlog is limited to 100 connections
bitpoll-0.11.0-app | your mercy for graceful operations on workers is 60 seconds
bitpoll-0.11.0-app | mapped 938592 bytes (916 KB) for 32 cores
bitpoll-0.11.0-app | *** Operational MODE: preforking+threaded ***
bitpoll-0.11.0-app | WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x788dd2c34390 pid: 7 (default app)
bitpoll-0.11.0-app | *** uWSGI is running in multiple interpreter mode ***
bitpoll-0.11.0-app | spawned uWSGI master process (pid: 7)
bitpoll-0.11.0-app | spawned uWSGI worker 1 (pid: 11, cores: 4)
bitpoll-0.11.0-app | spawned uWSGI worker 2 (pid: 15, cores: 4)
六、数据库初始化验证
迁移与建表由应用入口脚本自动完成,无需手工干预。验证表结构就绪:
docker compose exec -T db psql -U bitpoll -d bitpoll -c "select count(*) from django_migrations;"
实测回显:
count
-------
64
(1 row)
64 条迁移记录全部落库。业务表清单(\dt 节选):poll_poll、poll_choice、poll_choicevalue、poll_vote、poll_comment、base_bitpolluser、caldav_davcalendar 等 23 张。
七、管理员初始化
应用不提供自动建管理员,需进应用服务执行 Django 命令:
docker compose exec bitpoll python3 /opt/bitpoll/manage.py createsuperuser
按提示输入用户名 admin、邮箱 admin@quzuo.net、密码 q1w2e3r4(交互回显与更多管理命令见本专栏《Bitpoll 0.11.0 常用 CLI 命令详解》)。
八、全链路验证
8.1 首页可达
curl -sI http://127.0.0.1:8136/ | head -6
curl -s http://127.0.0.1:8136/ | grep -oE '<title>[^<]*</title>'
实测回显:
HTTP/1.1 200 OK
Server: nginx/1.23.4
Date: Sun, 01 Nov 2026 11:53:20 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 9741
Connection: keep-alive
<title>Create poll - Bitpoll</title>
首页即「创建投票」页(未登录可匿名建投票,由 PUBLIC_POLLS/注册开关控制)。响应头里 Content-Security-Policy-Report-Only、X-Frame-Options: DENY 等安全头由 django-simple-csp 中间件自动注入。
8.2 登录 admin
TOKEN=$(curl -s -c cookies.txt "http://127.0.0.1:8136/login/" | grep -oE 'csrfmiddlewaretoken" value="[^"]+' | head -1 | cut -d'"' -f3)
curl -s -b cookies.txt -c cookies.txt -e "http://127.0.0.1:8136/login/" \
-d "csrfmiddlewaretoken=$TOKEN&username=admin&password=q1w2e3r4&next=/polls/manage" \
-o /dev/null -w "login HTTP %{http_code} redirect=%{redirect_url}\n" \
"http://127.0.0.1:8136/login/"
实测回显:
login HTTP 302 redirect=http://127.0.0.1:8136/polls/manage
8.3 登录态确认
curl -s -b cookies.txt http://127.0.0.1:8136/settings/ | grep -oE '<title>[^<]*</title>'
实测回显:
<title>Settings - Bitpoll</title>
/settings/ 需登录态才能访问,返回 200 即会话生效。
8.4 投票创建与访问
以登录态 POST 创建投票(表单流程与 Web 界面一致),成功后 302 进入选项编辑页:
HTTP/1.1 302 Found
Location: /poll/sprint-review/edit/choices/universal/
随后访问投票页验证:
curl -s http://127.0.0.1:8136/poll/sprint-review/ | grep -oE '<title>[^<]*</title>'
实测回显:
<title>Sprint 回顾表决 - Bitpoll</title>
投票支持中文标题,时间戳、选项、投票人矩阵渲染正常;/poll/<url>.csv 可直接导出结果。
九、持久化与重启验证
docker compose down 后再 up -d,数据库与投票数据完整保留(bind mount 目录 data/postgres 不随编排销毁):
docker compose down
docker compose up -d
curl -s http://127.0.0.1:8136/poll/weekly-meeting/ | grep -oE '<title>[^<]*</title>'
实测回显:
<title>项目周会时间投票 - Bitpoll</title>
restart: unless-stopped 保证服务器重启后三服务自动拉起;静态目录 run/static 每次应用启动时由入口脚本清空重灌,无需人工维护。
十、备份与升级
- 备份:
docker compose exec -T db pg_dump -U bitpoll bitpoll > backup.sql(实测产物 100KB 级),连同run/config/、keys.env一起归档。恢复用psql -U bitpoll -d bitpoll < backup.sql。 - 升级:先备份,再
docker compose pull && docker compose up -d;入口脚本自动跑增量迁移。回滚即恢复旧备份 + 指定旧发布线重新拉起。
十一、排障实录
- 照抄官方 compose 起不来:官方
compose.yaml无数据库服务,应用启动即报OperationalError: could not connect to server并循环重启。解决:按本文第二节补db服务与depends_on: condition: service_healthy。 - 登录 POST 返回 404:页面模板里的
/account/login/只是注册流程的中转路径,对它 POST 会 404;真正的登录端点是站点根下/login/(DjangoLoginView挂载点)。用curl -d ... http://127.0.0.1:8136/login/后返回 302,会话建立。 - 首页 400 DisallowedHost:
settings.py未配ALLOWED_HOSTS时,通过 IP+端口访问被 Django 拒绝。解决:ALLOWED_HOSTS = ['*'](临时)或写入实际域名/IP 白名单(生产)。 - 静态资源 404 页面裸奔:
run/static目录未挂载给 nginx 服务,或应用还没跑过一次启动(静态文件由入口脚本从应用包内复制到run/static)。确认两个服务共享同一./run/static目录,且应用启动日志无报错后刷新即可。 - 数据库就绪竞态:去掉健康检查后应用先于数据库完成初始化启动,报连接错误。解决:保留
pg_isready健康检查 +service_healthy条件,本文 5.2 回显中Waiting → Healthy即该机制生效。
十二、收尾清单
-
settings.py中ALLOWED_HOSTS收紧为实际域名 -
BASE_URL改为对外正式地址(影响通知邮件里的链接) -
EMAIL_BACKEND从 console 换为真实 SMTP - 删除
8137:3009端口映射(开发端口不对外) -
keys.env、run/config/、data/权限收敛(600/700,属主统一) - 配置
data/postgres与backup.sql的定期异地备份
部署完成后,配置项逐项含义见《Bitpoll 0.11.0 配置参数详解》,日常运维命令与 API 实操见《Bitpoll 0.11.0 常用 CLI 命令详解》。