Baby Buddy 安装部署指南(Ubuntu 22.04 直装实录)
Ubuntu 22.04 直装 Baby Buddy v2.11.0 全过程实录:Python venv + PostgreSQL 14 + Node 构建静态资源 + Gunicorn + systemd + Nginx,含登录路由、数据库切换与构建告警等五个真实排障现场。
Baby Buddy 安装部署指南(Ubuntu 22.04 直装实录)
> 本文是 Baby Buddy v2.11.0 在 Ubuntu 22.04 服务器上的直装实录:全部命令与回显均来自真机操作,无一处编造。组件定位与技术选型背景见 《Baby Buddy 介绍与选型指南》,参数级调优见 《Baby Buddy 配置参数详解》,命令级运维见 《Baby Buddy 常用 CLI 命令详解》。
一、部署架构与前置条件
Baby Buddy 是 Django 应用,单机直装的标准形态是一条三层链路:
浏览器 ──> Nginx (80, 静态资源直出 + 反向代理)
└──> Gunicorn (127.0.0.1:8000, WSGI)
└──> PostgreSQL (127.0.0.1:5432)
各层职责与版本(均为本文实测环境实查值):
| 层 | 组件 | 实测版本 | 职责 |
|---|---|---|---|
| 接入层 | Nginx | 1.18.0 (Ubuntu) | 80 端口对外;/static/、/media/ 直出;其余代理到 Gunicorn |
| 应用层 | Gunicorn | 23.0.0 | 3 个 sync worker 绑定 127.0.0.1:8000,systemd 托管 |
| 数据层 | PostgreSQL | 14.24 (Ubuntu) | 业务库 babybuddy,属主 babybuddy |
| 运行时 | Python | 3.10.12 | venv 隔离,Django 5.2.17 + DRF 3.18.1 |
| 构建时 | Node.js | 24.22.1 | 仅构建期使用:npm 依赖 + gulp 编译 SCSS/JS |
端口说明:本文实验服务器位于 10.89.71.0/24 网段(主机名
babybuddy-server,内网 IP 10.89.71.5),Nginx 监听 80。管理员从运维网段访问时走网关发布端口 8147(网关将 8147 映射到服务器 80),因此文中同时出现:80(服务器本机)与:8147(跨网段访问)两种端口的回显,属同一服务的不同访问路径。
前置条件清单:
- Ubuntu 22.04 LTS 服务器一台,root 权限;
- 服务器可访问外网(apt、npm、pip 均需联网;本文代理环境为
192.168.1.62:7897,无代理环境直接执行即可); - 磁盘 2 GB 以上(源码 + venv + node_modules + 静态资源合计约 1.2 GB)。
二、系统准备与依赖安装
2.1 系统包
一次性装齐系统依赖(PostgreSQL、Nginx、Python 编译头文件、git):
apt update
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq postgresql postgresql-contrib \
nginx git python3 python3-venv python3-dev libpq-dev build-essential
实测回显(摘取末尾关键行):
Setting up postgresql-14 (14.24-0ubuntu0.22.04.1) ...
Setting up postgresql-contrib (14+238ubuntu1) ...
Setting up nginx (1.18.0-6ubuntu14.7) ...
Processing triggers for libc-bin (2.35-0ubuntu3.11) ...
启动数据层并确认:
systemctl start postgresql
systemctl enable postgresql nginx
psql --version
nginx -v
python3 --version
实测回显:
psql (PostgreSQL) 14.24 (Ubuntu 14.24-0ubuntu0.22.04.1)
nginx version: nginx/1.18.0 (Ubuntu)
Python 3.10.12
2.2 Node.js 24(仅构建机需要)
Ubuntu 22.04 自带的 Node 12 过老,用 NodeSource 官方源装 24.x:
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
apt-get install -y nodejs
node -v && npm -v
实测回显:
v24.22.1
11.19.1
Node 只服务于「前端静态资源构建」这一步(第五节)。若部署机不方便装 Node,可在任一台同架构机器上构建完成后把
static/目录整体拷贝过来——Baby Buddy 的静态产物是自包含的(Whitenoise 带哈希清单)。
三、PostgreSQL 初始化
创建业务账号与数据库(密码用强随机串,本文示例统一 q1w2e3r4):
su postgres -c "psql -c \"CREATE USER babybuddy WITH PASSWORD 'q1w2e3r4';\""
su postgres -c "psql -c \"CREATE DATABASE babybuddy OWNER babybuddy;\""
su postgres -c "psql -c '\l' | grep babybuddy"
实测回显:
CREATE ROLE
CREATE DATABASE
babybuddy | babybuddy | UTF8 | C.UTF-8 | C.UTF-8 |
PostgreSQL 14 默认 pg_hba.conf 对本地连接走 peer/scram-sha-256,Baby Buddy 走 TCP 127.0.0.1 连接无需改认证配置,DB_HOST=127.0.0.1 + 密码即可直连。
四、运行账户与源码部署
遵循最小权限原则:Baby Buddy 以专用系统用户运行,不赋 sudo。
useradd -r -m -d /opt/babybuddy -s /usr/sbin/nologin babybuddy
注意:
-s /usr/sbin/nologin让服务账户不可登录(安全最佳实践),代价是后面所有manage.py操作必须用su -s /bin/bash babybuddy -c方式执行。这个取舍在第十一节排障实录 #2 里有真实教训。
拉取源码并固定到 v2.11.0 tag:
cd /opt/babybuddy
git clone --branch v2.11.0 --depth 1 https://github.com/babybuddy/babybuddy.git app
chown -R babybuddy:babybuddy /opt/babybuddy
版本自证(源码内嵌版本号):
grep __version__ /opt/babybuddy/app/babybuddy/__init__.py
实测回显:
__version__ = "2.11.0"
五、Python 虚拟环境与后端依赖
su -s /bin/bash babybuddy -c "cd /opt/babybuddy/app && python3 -m venv venv"
su -s /bin/bash babybuddy -c "cd /opt/babybuddy/app && venv/bin/pip install -q -r requirements.txt"
实测关键依赖版本(venv/bin/pip list 摘取):
Django 5.2.17
djangorestframework 3.18.1
gunicorn 23.0.0
django-axes 7.1.0
django-dbsettings 1.4.0
psycopg2-binary 2.9.13
pillow 12.3.0
psycopg2-binary 已在 requirements.txt 中预置,无需单独编译;build-essential 与 libpq-dev 兜底的是个别需要源码编译的 wheel 场景。
六、前端静态资源构建(Node 24 + gulp 实录)
Baby Buddy 的 CSS/JS 图标等静态资源需要本地构建,产物落到 static/ 目录。
su -s /bin/bash babybuddy -c "cd /opt/babybuddy/app && npm ci"
su -s /bin/bash babybuddy -c "cd /opt/babybuddy/app && npx gulp build"
gulp build 等价于并行执行 extras + scripts + styles 三个子任务,实测回显:
[04:15:16] Finished 'styles' after 1.89 s
[04:15:22] Finished 'scripts' after 7.75 s
[04:15:23] Finished 'extras' after 9.23 s
[04:15:23] Finished 'build' after 9.24 s
构建过程中的真实告警(排障实录):
WARNING: 78 repetitive deprecation warnings omitted.
Run in verbose mode to see all warnings.
node_modules/bootstrap/scss/_functions.scss 217:11 @import
node_modules/bootstrap/scss/bootstrap.scss 7:9 @import
- 2:9 root stylesheet
解读:v2.11.0 的依赖树里 Bootstrap 5 仍以 @import 方式引用 SCSS,dart-sass 1.104 会发出大量「@import 已弃用」告警。告警可忽略——构建正常结束、产物完整。真正会让构建失败的坑有两个:
- 老教程装
node-sass:node-sass(libsass)不支持 Node 17+,在 Node 24 上必然编译失败。Baby Buddy v2.11.0 用的是gulp-sass 6.x + sass(dart-sass)1.104,不要额外安装 node-sass; - npm ci 与 package-lock.json 版本漂移:v2.11.0 的锁文件基于 npm 9/10 生成,npm 11 执行
npm ci偶发 peer 依赖告警,属正常;若报EUSAGE不兼容,改用npm install后再构建。
构建完成后产物自证:
ls /opt/babybuddy/app/static/babybuddy/css | head -3
实测回显:
app.min.css
base.css
base.min.css
七、环境配置文件 .env
Baby Buddy 用 python-dotenv 自动加载工作目录下的 .env(load_dotenv(find_dotenv()),支持向上查找),全部配置项含义见 《Baby Buddy 配置参数详解》。直装最小集如下:
cat > /opt/babybuddy/app/.env <<'EOF'
DJANGO_SETTINGS_MODULE=babybuddy.settings.base
SECRET_KEY=<替换为 python3 -c "import secrets; print(secrets.token_urlsafe(48))" 的输出>
ALLOWED_HOSTS=*
DEBUG=False
TZ=Asia/Shanghai
DB_ENGINE=django.db.backends.postgresql
DB_NAME=babybuddy
DB_USER=babybuddy
DB_PASSWORD=q1w2e3r4
DB_HOST=127.0.0.1
DB_PORT=5432
EOF
chown babybuddy:babybuddy /opt/babybuddy/app/.env
chmod 600 /opt/babybuddy/app/.env
两个易混项提前说清(实测验证过的行为):
TZ是操作系统时区(影响进程日志时间戳),实测sendtestemail的邮件头时间戳跟随它;- Django 的
TIME_ZONE在 settings 里硬编码为UTC,数据库时间统一以 UTC 存储,界面显示时区是每用户在个人设置里选的(由UserTimezoneMiddleware注入)。想改全站默认时区不是改.env,详见配置篇。
八、数据库迁移、静态文件与超级用户
依次执行三连(务必以 babybuddy 身份,避免产生 root 属主文件):
cd /opt/babybuddy/app
su -s /bin/bash babybuddy -c "cd /opt/babybuddy/app && venv/bin/python manage.py migrate --noinput"
实测回显(38 个 core 迁移全过,摘取首尾):
Applying core.0032_child_birth_time... OK
Applying core.0033_heightpercentile_and_more... OK
...
Applying core.0038_tummytime_notes... OK
Applying sessions.0001_initial... OK
静态文件收集(Whitenoise 压缩 + 哈希):
su -s /bin/bash babybuddy -c "cd /opt/babybuddy/app && venv/bin/python manage.py collectstatic --noinput"
实测回显:
0 static files copied to '/opt/babybuddy/static', 183 unmodified, 446 post-processed.
创建超级用户(示例账号 admin / q1w2e3r4):
su -s /bin/bash babybuddy -c "cd /opt/babybuddy/app && DJANGO_SUPERUSER_PASSWORD=q1w2e3r4 venv/bin/python manage.py createsuperuser --noinput --username admin --email admin@example.com"
若此前已经跑过一次 migrate,再次执行会报
CommandError: Error: That username is already taken.——用户已存在时改用manage.py changepassword admin重置口令即可。
九、Gunicorn 与 systemd 服务
Gunicorn 绑定本机回环,由 Nginx 对外:
# /etc/systemd/system/babybuddy.service
[Unit]
Description=Baby Buddy (gunicorn daemon)
After=network.target
[Service]
Type=notify
User=babybuddy
Group=babybuddy
WorkingDirectory=/opt/babybuddy/app
Environment="DJANGO_SETTINGS_MODULE=babybuddy.settings.base"
ExecStart=/opt/babybuddy/app/venv/bin/gunicorn babybuddy.wsgi:application --workers 3 --bind 127.0.0.1:8000 --timeout 30 --access-logfile - --error-logfile -
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
启用并启动:
systemctl daemon-reload
systemctl enable --now babybuddy
systemctl is-active babybuddy
实测回显:
active
参数说明:
Type=notify:Gunicorn 23 原生支持 sd_notify,systemd 等「Gunicorn arbiter booted」才算启动成功,避免端口未就绪就被 Nginx 打穿;--workers 3:官方建议2×CPU+1,家庭规模 2~3 个足够;- worker 间会话缓存为默认 LocMem,无需 Redis(Baby Buddy 默认缓存走数据库表
cache_default,见配置篇)。
十、Nginx 反向代理
官方布局:静态/媒体由 Nginx 直出,动态请求代理到 Gunicorn。
# /etc/nginx/sites-available/babybuddy
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
client_max_body_size 20M;
location /static/ {
alias /opt/babybuddy/app/static/;
expires 7d;
}
location /media/ {
alias /opt/babybuddy/app/media/;
expires 7d;
}
location / {
proxy_pass http://127.0.0.1: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;
proxy_read_timeout 60s;
}
}
ln -sf /etc/nginx/sites-available/babybuddy /etc/nginx/sites-enabled/babybuddy
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx
实测回显:
nginx: configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
十一、首次启动验证(登录路由实录)
登录页路由是 /login/(不是很多教程写的 /accounts/login/)。完整实测链路:
curl -s -o /dev/null -w 'root %{http_code} redirect->%{redirect_url}\n' http://127.0.0.1:8147/
curl -s -o /dev/null -w 'accounts/login %{http_code}\n' http://127.0.0.1:8147/accounts/login/
curl -s -o /dev/null -w 'login %{http_code}\n' http://127.0.0.1:8147/login/
实测回显:
root 302 redirect->http://127.0.0.1:8147/login/?next=/
accounts/login 404
login 200
带 CSRF 表单登录(浏览器无感,这里用 curl 复现完整两步):
CSRF=$(curl -s -c cj.txt http://127.0.0.1:8147/login/ | grep -oP 'csrfmiddlewaretoken" value="\K[^"]+' | head -1)
curl -s -b cj.txt -c cj.txt -o /dev/null -w 'login POST %{http_code} -> %{redirect_url}\n' \
-d "csrfmiddlewaretoken=$CSRF&username=admin&password=q1w2e3r4&next=/" \
-H 'Referer: http://127.0.0.1:8147/login/' http://127.0.0.1:8147/login/
curl -s -L -b cj.txt http://127.0.0.1:8147/ -o dash.html -w 'final %{url_effective} %{http_code}\n'
实测回显:
login POST 302 -> http://127.0.0.1:8147/
final http://127.0.0.1:8147/welcome/ 200
两个关键行为(都是实测得到的真实行为,别当成 bug):
- 登录成功 =
POST /login/返回 302 → /,随后 sessionid 写入 cookie; - 首次登录没有儿童数据时,
/会 302 到/welcome/——这是 Baby Buddy 的初始化向导,按页面提示创建第一个儿童档案后,/才会落到真正的仪表盘。创建儿童后实测:
final http://127.0.0.1:8147/children/xiaoxin-nohara/dashboard/ 200
跨网段浏览器访问 http://<网关IP>:8147/,出现深色主题登录页即部署成功。
十二、排障实录(五个真实现场)
#1 数据库初始化脚本「卡死」:把 apt 安装 + systemctl start postgresql + psql 建库 串在一条 shell 里执行时,进程停在 systemctl start postgresql 数分钟不动,且后续 CREATE USER 未执行。根因是串联脚本里用了 sudo(最小化系统未安装,报 sudo: command not found 也会挂起整条管道),加上 systemd start 与 apt 管道互相等待。处置:拆步骤执行,最小化环境用 su postgres -c 替代 sudo -u postgres;先确认 ps aux | grep systemctl 无残留再继续。
#2 su babybuddy 报 This account is currently not available:服务账户 shell 是 nologin,su babybuddy -c 直接被拒。处置:su -s /bin/bash babybuddy -c "..." 显式指定 shell。这是选择 -r 创建系统账户的必然代价。
#3 切了 PostgreSQL 却查无 auth_user 表:.env 加了 DB_* 但 psql 里 \dt 找不到 Django 表。根因:修改 .env 前应用曾以默认 SQLite 跑过 migrate(数据落在了 data/db.sqlite3),PostgreSQL 库还是空的。处置:DB_* 写好后必须 systemctl restart babybuddy 再执行 manage.py migrate;.env 是启动时一次性读入的。验证手段:
su postgres -c "psql -d babybuddy -c 'select count(*) from auth_user;'"
实测回显:
count
-------
1
(1 row)
#4 Host 校验 400:ALLOWED_HOSTS 收紧为具体域名后,用 IP 或错误 Host 访问一律 400 Bad Request(本文实测:Host: evil.com -> 400,Host: babybuddy.example.com -> 200)。临时排查时把 ALLOWED_HOSTS=* 后需要重启进程才生效——改配置不重启是白改。
#5 连锁失败登录被锁:连续 5 次密码错误后(AXES_FAILURE_LIMIT=5),即使密码正确也返回 429,锁定页文案为「Account locked: too many login attempts. Please try again later.」,默认冷却 1 小时(AXES_COOLOFF_TIME=1)。立即解锁:
su -s /bin/bash babybuddy -c "cd /opt/babybuddy/app && venv/bin/python manage.py axes_reset_username admin"
实测回显:
1 attempts removed.
更多命令级操作与备份恢复见 《Baby Buddy 常用 CLI 命令详解》。
十三、部署验证清单
| 验证项 | 命令 | 期望结果 |
|---|---|---|
| 数据库 | systemctl is-active postgresql | active |
| 应用 | systemctl is-active babybuddy | active(Status: Gunicorn arbiter booted) |
| 接入层 | nginx -t | syntax is ok / test is successful |
| 登录页 | curl -I http://127.0.0.1:80/login/ | HTTP 200 |
| 登录动作 | 表单 POST | 302 → /,有 sessionid |
| 静态资源 | 浏览器 DevTools | /static/babybuddy/css/app.*.css 200 |
| 首页 | 登录后无数据 | 302 → /welcome/ |
下一步建议:按 《Baby Buddy 配置参数详解》 收紧 ALLOWED_HOSTS、配置 HTTPS 头与 SMTP;用 《Baby Buddy 常用 CLI 命令详解》 里的 fake 命令生成演示数据熟悉界面,再为生产库制定 pg_dump 备份计划。