Psono v7.5.3 Docker Compose 部署指南

Psono v7.5.3 Docker Compose 部署实测:组合镜像与 PostgreSQL 18 双服务编排、密钥生成、首管理员创建、健康检查三项自检、time_sync 400 排障实录与可选 Redis 缓存增强,全部命令与回显实测于 2026-10-24。

Psono密码管理自托管Docker Compose2026/10/24

Psono v7.5.3 Docker Compose 部署指南

目标:在一台 Ubuntu 22.04 服务器上,用 Docker Compose 从零部署 Psono 社区版 v7.5.3(组合镜像 + PostgreSQL 18 + 可选 Redis 缓存),完成密钥生成、数据库初始化、首个管理员创建与 Web/管理门户登录验证。全文命令与回显均在 Ubuntu 22.04 + Docker 29.8.1 环境实测于 2026-10-24。

一、环境信息

本文实测环境与部署规划如下,读者按自己的服务器调整 IP 与端口即可:

  • 操作系统:Ubuntu 22.04(x86_64)
  • Docker:29.8.1(含 Compose v2 插件)
  • Psono:v7.5.3(Build 5638426c,psono/psono-combo:latest 实测回显)
  • PostgreSQL:postgres:18-alpine(官方安装文档指定)
  • Redis(可选):redis:8-alpine(缓存与限流计数后端)
  • 服务器 IP:192.168.146.128(示例,替换为你的服务器地址)
  • Web 端口:8153(映射到组合镜像内部 80)
  • 部署目录:/opt/docker/psono/(配置)与 /opt/docker/psono/pgdata/(数据库数据)
  • Compose 项目名:psono-7.5.3-stack(显式命名,避免多栈环境下被同名项目误删)

二、镜像选型说明

Psono 官方在 Docker Hub 发布多个镜像,选型依据官方安装文档(doc.psono.com「Install Psono CE」,2026-10-24 实查):

镜像内容适用
psono/psono-combo服务端 + Web 客户端 + 管理门户 + Nginx推荐默认,一个服务搞定全部 Web 面
psono/psono-server仅服务端(Daphne ASGI)静态资源独立部署/多实例扩展
psono/psono-client仅 Web 客户端静态文件自建 CDN/独立站点
postgres:18-alpine数据库(官方安装文档指定版本)唯一强依赖存储

关键差异与如何选型:combo 与「分离部署」的功能完全一致,差异只在打包方式。首次部署、单机部署、无 CDN 需求,一律选 psono/psono-combo;只有当 Web 静态资源需要单独扩容或接入 CDN 时才拆分。本文采用 combo + PostgreSQL 双服务形态,Redis 作为可选增强(第六章)。镜像版本策略:本文实测时 latest 即 v7.5.3(服务端 /server/info/ 回显 "version": "7.5.3 (Build 5638426c)"),生产环境建议固化到具体版本标签,避免 latest 隐式升级。

三、前置条件

  1. Ubuntu 22.04 服务器,已安装 Docker 与 Compose 插件:
docker version --format '{{.Server.Version}}'
docker compose version

实测回显:

29.8.1
Docker Compose version v2.39.2
  1. 服务器时间与 NTP 可达性:Psono 健康检查会校验时间同步(默认对 time.google.com),境内网络需提前确认替代 NTP 源可达(第五章实测排障)。
  2. 规划端口:本文用 8153(确认未被占用):
ss -ltn | grep 8153 || echo "8153 空闲"

实测回显:

8153 空闲
  1. 创建部署目录:
mkdir -p /opt/docker/psono/pgdata
cd /opt/docker/psono

四、部署步骤

4.1 生成服务端密钥

Psono 安装的第一步是生成 7 个服务端密钥。官方命令通过组合镜像执行内置脚本 generateserverkeys.py:

docker run --rm -ti psono/psono-combo:latest python3 ./psono/generateserverkeys.py

实测回显(密钥为本次实测环境随机生成,读者执行会得到不同值;ADMIN_RECOVERY_PRIVATE_KEY 是恢复私钥,必须离线打印保存,严禁写入 settings.yaml 或留在服务器):

# Copy paste this content into your settings.yml and replace existing occurrences
#
# WARNING: Do this only for a fresh installation!
# Changing those variables afterwards will break the program e.g.:
# Activation links will not work, Server will not be able to read user emails, ...

SECRET_KEY: 'rIZ7Jqj20eS72dTcfTaFToQ4m928r8DI4G72nTtEhch2Q7yrefxdPF41W6UB1t8e'
ACTIVATION_LINK_SECRET: 'UXwBVwJ8NGzkQom6rseTijcVNAVgkcHTk0S0Vdksl7HvAyoP31NsfWYHtGgNkuXq'
DB_SECRET: 'RE0bhLwLAlwTU2Yz4QRS54dgfaNOjCgZ3tdA6QdchtBLecgJl3GsOYRiRxjos8H8'
EMAIL_SECRET_SALT: '$2b$12$Fsmqo9X3928MUmaQNETElu'
PRIVATE_KEY: '450197ac4b435a0ef69340213aef0fc354cb00347f23d3514dd5c735dae7f364'
PUBLIC_KEY: 'b97434da89fe500ee68410af75d4d41c99eb50975e99c9eec064d522601f7845'
ADMIN_RECOVERY_PUBLIC_KEY: '9238938b674a00effc2d610842c81bd5dbf93f606cacabd54590962486349d44'

# ============================================================================
# OFFLINE ADMIN RECOVERY PRIVATE KEY
#
# IMPORTANT: Print this private key and store the printout securely offline.
# Do NOT copy it into settings.yml or keep it on the server.
# Anyone with this key may be able to decrypt protected recovery data.
# ============================================================================
ADMIN_RECOVERY_PRIVATE_KEY: 'c0641f94e3ba4878558e50c87cc3595e631aa5e5422b592a1201cb9e5b60a1e9'

⚠️ 注意:该脚本在非交互终端(如自动化脚本)中执行时,-ti 参数会报错 cannot attach stdin to a TTY-enabled container because stdin is not a terminal,去掉 -t 只保留 -i 或两者都去掉即可。

4.2 编写 settings.yaml

在 /opt/docker/psono/ 创建 settings.yaml,将 4.1 生成的密钥填入前 7 行,数据库口令替换为你的强口令:

SECRET_KEY: 'rIZ7Jqj20eS72dTcfTaFToQ4m928r8DI4G72nTtEhch2Q7yrefxdPF41W6UB1t8e'
ACTIVATION_LINK_SECRET: 'UXwBVwJ8NGzkQom6rseTijcVNAVgkcHTk0S0Vdksl7HvAyoP31NsfWYHtGgNkuXq'
DB_SECRET: 'RE0bhLwLAlwTU2Yz4QRS54dgfaNOjCgZ3tdA6QdchtBLecgJl3GsOYRiRxjos8H8'
EMAIL_SECRET_SALT: '$2b$12$Fsmqo9X3928MUmaQNETElu'
PRIVATE_KEY: '450197ac4b435a0ef69340213aef0fc354cb00347f23d3514dd5c735dae7f364'
PUBLIC_KEY: 'b97434da89fe500ee68410af75d4d41c99eb50975e99c9eec064d522601f7845'
ADMIN_RECOVERY_PUBLIC_KEY: '9238938b674a00effc2d610842c81bd5dbf93f606cacabd54590962486349d44'

# Web 客户端地址(不含尾斜杠)
WEB_CLIENT_URL: 'http://192.168.146.128:8153'

# NTP 时间服务器(默认 time.google.com,境内不可达时替换)
TIME_SERVER: 'ntp.aliyun.com'

# 反向代理层数(组合镜像内置 Nginx 一层 + 若前面还有反代则再加)
NUM_PROXIES: 2

# 生产环境必须为 False
DEBUG: False

ALLOWED_HOSTS: ['*']

# 用户名域名部分(登录页展示的域名后缀)
ALLOWED_DOMAINS: ['example.com']

# 服务器对外地址(激活链接等以此生成)
HOST_URL: 'http://192.168.146.128:8153/server'

# 发信邮箱(未配置邮件时可用命令行创建用户,不影响部署验证)
EMAIL_FROM: 'psono@example.com'
EMAIL_HOST: 'smtp.example.com'
EMAIL_HOST_USER: ''
EMAIL_HOST_PASSWORD: ''
EMAIL_PORT: 25
EMAIL_SUBJECT_PREFIX: ''
EMAIL_USE_TLS: False
EMAIL_USE_SSL: False
EMAIL_SSL_CERTFILE:
EMAIL_SSL_KEYFILE:
EMAIL_TIMEOUT: 10

# 管理门户 API(门户必需,默认 False)
MANAGEMENT_ENABLED: True

# PostgreSQL 连接(HOST 为 compose 服务名 psono-db)
DATABASES:
    default:
        'ENGINE': 'django.db.backends.postgresql_psycopg2'
        'NAME': 'psono'
        'USER': 'psono'
        'PASSWORD': 'q1w2e3r4'
        'HOST': 'psono-db'
        'PORT': '5432'

TEMPLATES: [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['/root/psono/templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

逐项说明:7 个密钥决定加密与链接签名行为,一经使用不可更改(官方警告:改了会导致激活链接失效、邮件解密失败);WEB_CLIENT_URL/HOST_URL 必须是用户浏览器可达的地址;ALLOWED_DOMAINS 决定用户名的域名部分(如 root@example.com);NUM_PROXIES: 2 对应「组合镜像内置 Nginx + 外层反向代理」的默认链路,若直接裸暴露 8153 不再套外层反代,可设为 1。配置参数全集与调优见本专栏《Psono v7.5.3 配置参数详解》。

4.3 编写 docker-compose.yml

创建 /opt/docker/psono/docker-compose.yml:

name: psono-7.5.3-stack

services:
  psono-db:
    image: postgres:18-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: psono
      POSTGRES_PASSWORD: q1w2e3r4
      POSTGRES_DB: psono
    volumes:
      - ./pgdata:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U psono -d psono"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - psono-net

  psono-server:
    image: psono/psono-combo:latest
    restart: unless-stopped
    sysctls:
      net.core.somaxconn: 65535
    volumes:
      - ./settings.yaml:/root/.psono_server/settings.yaml:ro
      - ./config.json:/usr/share/nginx/html/config.json:ro
      - ./config.json:/usr/share/nginx/html/portal/config.json:ro
    ports:
      - "8153:80"
    depends_on:
      psono-db:
        condition: service_healthy
    networks:
      - psono-net

networks:
  psono-net:
    driver: bridge
    name: psono-753-net
    ipam:
      config:
        - subnet: 10.89.58.0/24

配置要点逐项说明:name: 显式固定项目名,多套 compose 栈并存时防止 docker compose down 误伤其他项目;数据库密码必须与 settings.yaml 的 DATABASES.PASSWORD 一致;健康检查让 psono-server 等数据库真正就绪后再启动,避免首启迁移失败;somaxconn=65535 是官方启动命令中的原样参数;子网显式指定为 10.89.58.0/24(创建前先 docker network ls 确认该网段空闲,冲突时换一段)。

再创建组合镜像所需的 Web 客户端配置 /opt/docker/psono/config.json(同时挂给用户端与门户端):

{
    "backend_servers": [
        {
            "title": "Psono Self-Hosted",
            "url": "http://192.168.146.128:8153/server"
        }
    ],
    "base_url": "http://192.168.146.128:8153/",
    "allow_custom_server": true,
    "allow_registration": true,
    "allow_lost_password": true,
    "disable_download_bar": false,
    "remember_me_default": false,
    "trust_device_default": false,
    "authentication_methods": [
        "AUTHKEY"
    ],
    "saml_provider": []
}

说明:backend_servers[].url 指向服务端地址,title 显示在登录页服务器选择器中;authentication_methods 默认 AUTHKEY(口令认证),接入 SAML/LDAP 后在此追加。

4.4 启动与自动迁移

组合镜像的启动入口自带数据库迁移(先执行 manage.py migrate 再拉起 ASGI 服务),直接启动即可:

cd /opt/docker/psono
docker compose up -d

实测回显:

 Container psono-753-stack-psono-db-1 Started
 Container psono-753-stack-psono-db-1 Waiting
 Container psono-753-stack-psono-db-1 Healthy
 Container psono-753-stack-psono-server-1 Starting
 Container psono-753-stack-psono-server-1 Started

确认两个服务的健康状态:

docker compose ps --format 'table {{.Name}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'

实测回显:

NAME                             IMAGE                      STATUS                             PORTS
psono-753-stack-psono-db-1       postgres:18-alpine         Up 35 seconds (healthy)            5432/tcp
psono-753-stack-psono-server-1   psono/psono-combo:latest   Up 25 seconds (health: starting)   0.0.0.0:8153->80/tcp, [::]:8153->80/tcp

服务端健康检查通过后(约 30–60 秒)状态变为 (healthy)。

4.5 验证服务版本与健康状态

打开服务信息端点(返回签名 JSON,验证部署版本):

curl -s "http://127.0.0.1:8153/server/info/"

实测回显(节选,完整响应含 signature/verify_key 签名字段):

{"info":"{\"version\": \"7.5.3 (Build 5638426c)\", \"api\": 1, \"log_audit\": false, \"public_key\": \"b97434da89fe500ee68410af75d4d41c99eb50975e99c9eec064d522601f7845\", \"admin_recovery_public_key\": \"9238938b674a00effc2d610842c81bd5dbf93f606cacabd54590962486349d44\", \"authentication_methods\": [\"AUTHKEY\"], \"web_client\": \"http://192.168.146.128:8153\", \"management\": true, \"files\": true, \"allowed_file_repository_types\": [\"azure_blob\", \"gcp_cloud_storage\", \"aws_s3\", \"do_spaces\", \"backblaze\"], \"favicon_service_url\": \"https://favicon.psono.com/v1/icon/\", \"allowed_second_factors\": [\"yubikey_otp\", \"webauthn\", \"google_authenticator\", \"duo\"], \"multifactor_enabled\": false, \"type\": \"CE\", ...}","signature":"69147b26…","verify_key":"b4eb25cc…"}

关键信息:version 确认版本与构建号;type: "CE" 确认社区版;management: true 确认管理门户 API 已开。

健康检查端点(数据库读写 + 时间同步三项自检):

curl -s -w "\nHTTP=%{http_code}\n" "http://127.0.0.1:8153/server/healthcheck/"

实测回显:

{"db_read":{"healthy":true},"db_sync":{"healthy":true},"time_sync":{"healthy":true},"_info":{"ip":"10.89.58.1"}}
HTTP=200

三项全 true 且 HTTP 200 即部署成功。首次部署若 time_sync 为 false,见第七章 7.1 排障。

4.6 创建首个管理员

未配置邮件时的标准做法:命令行创建用户并提权。必须在 compose 网络内执行(docker compose exec),否则单发命令解析不了数据库主机名(见第七章 7.2):

docker compose exec psono-server python3 ./psono/manage.py createuser root@example.com q1w2e3r4 admin@example.com

实测回显:

Created user "root@example.com" with password "******" and email "admin@example.com"
docker compose exec psono-server python3 ./psono/manage.py promoteuser root@example.com superuser

实测回显:

Promoted user "root@example.com" to "superuser"

说明:createuser 三个参数依次是「用户名(必须形如邮箱,域名部分须在 ALLOWED_DOMAINS 内)、密码、联系邮箱」;演示口令为 q1w2e3r4,生产环境务必改强口令;promoteuser 仅首个管理员需要,之后可在门户内管理。

4.7 Web 客户端与管理门户登录验证

浏览器打开三个地址逐一验证(实测均返回 HTTP 200):

curl -s -o /dev/null -w "webclient HTTP=%{http_code}\n" "http://192.168.146.128:8153/"
curl -s -o /dev/null -w "portal HTTP=%{http_code}\n" "http://192.168.146.128:8153/portal/"
curl -s -o /dev/null -w "info HTTP=%{http_code}\n" "http://192.168.146.128:8153/server/info/"

实测回显:

webclient HTTP=200
portal HTTP=200
info HTTP=200
  • http://192.168.146.128:8153/:用户端 Web 保险库,用 root@example.com / q1w2e3r4 登录。
  • http://192.168.146.128:8153/portal/:管理门户,同一账号登录后可管理用户、分组、策略与合规设置。

⚠️ 注意:以上为 HTTP 验证路径。生产环境必须按官方反代文档前置 Nginx 做 TLS 卸载(官方提供含 CSP 头的完整 server 块,client_max_body_size 256m),并将 WEB_CLIENT_URL/HOST_URL/config.json 全部改为 HTTPS 域名。

五、数据持久化说明

数据落盘路径(宿主机)挂载点(服务内)备份要点
PostgreSQL 全部数据/opt/docker/psono/pgdata//var/lib/postgresql密码库唯一真实数据源
settings.yaml/opt/docker/psono/settings.yaml/root/.psono_server/settings.yaml含解密所需密钥,与数据库同等保密级别
Web/门户 config.json/opt/docker/psono/config.json/usr/share/nginx/html/config.json(及 portal/config.json)可随时重建

数据目录验证:

ls /opt/docker/psono/pgdata/ | head -8

实测回显:

PG_VERSION
pg_hba.conf
pg_ident.conf
postgresql.auto.conf
postgresql.conf

备份/恢复双件套:数据库(pg_dumpall 或物理备份 pgdata/)+ settings.yaml(离线保存)。丢失 settings.yaml 中的密钥等于丢失全部密码库——服务端密钥参与数据加密与链接签名,官方备份文档将两者并列为完整备份的必要条件。

升级与回滚:改 docker-compose.yml 中镜像标签到目标版本 → docker compose pull && docker compose up -d;组合镜像每次启动自动执行 migrate,升级前先备份数据库与 settings.yaml,回滚即把镜像标签改回旧版本重新 up -d(数据库迁移一般向后兼容一个次版本,跨大版本回滚需按官方 Update 文档核对)。

六、环境变量表(可选 Redis 缓存增强)

官方支持用环境变量替代 settings.yaml 全量配置(前缀 PSONO_,官方「Environment Variables」文档实查,2026-10-24),常用项如下:

环境变量对应 settings.yaml 键说明
PSONO_SECRET_KEYSECRET_KEY必填,主密钥
PSONO_PRIVATE_KEY / PSONO_PUBLIC_KEYPRIVATE_KEY / PUBLIC_KEY必填,服务器签名密钥对
PSONO_ACTIVATION_LINK_SECRETACTIVATION_LINK_SECRET必填,激活链接签名
PSONO_DB_SECRETDB_SECRET必填,数据库字段加密
PSONO_EMAIL_SECRET_SALTEMAIL_SECRET_SALT必填,邮件加密盐
PSONO_HOST_URLHOST_URL必填,对外服务地址
PSONO_DATABASE_URLDATABASES必填,URL 形式数据库连接
PSONO_ALLOWED_DOMAINSALLOWED_DOMAINS用户名域名白名单
PSONO_NUM_PROXIESNUM_PROXIES反代层数
PSONO_ALLOW_REGISTRATIONALLOW_REGISTRATION开放注册开关
PSONO_THROTTLE_RATE_LOGIN同名限流组登录限流速率
PSONO_MANAGEMENT_COMMAND_ACCESS_KEY—管理命令访问密钥

Redis 缓存增强:在 compose 中追加 psono-cache 服务(官方 settings.yaml 注释原文:「Cache enabled without belows Redis may lead to unexpected behaviour」——开启缓存必须同时配置 Redis)。追加 docker-compose.redis.yml:

name: psono-7.5.3-stack

services:
  psono-cache:
    image: redis:8-alpine
    restart: unless-stopped
    command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
    volumes:
      - ./redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - psono-net

networks:
  psono-net:
    external: true
    name: psono-753-net

同时在 settings.yaml 追加三行(官方默认用 13 号库,避开 0/1):

CACHE_ENABLE: True
CACHE_REDIS: True
CACHE_REDIS_LOCATION: 'redis://psono-cache:6379/13'

启动并验证缓存生效:

docker compose -f docker-compose.yml -f docker-compose.redis.yml up -d psono-cache
docker compose restart psono-server

实测验证:限流计数键已写入 Redis(Psono 的限流器以 Redis 为后端):

docker compose exec psono-cache redis-cli -n 13 dbsize
docker compose exec psono-cache redis-cli -n 13 keys '*:1:*'

实测回显:

3
b97434da89fe500ee68410af75d4d41c99eb50975e99c9eec064d522601f7845:7.5.3_(Build_5638426c):1:throttle_health_check_10.89.58.1
b97434da89fe500ee68410af75d4d41c99eb50975e99c9eec064d522601f7845:7.5.3_(Build_5638426c):1:throttle_user_10.89.58.1
b97434da89fe500ee68410af75d4d41c99eb50975e99c9eec064d522601f7845:7.5.3_(Build_5638426c):1:throttle_anon_10.89.58.1

重启后健康检查复验仍为 200(db_read/db_sync/time_sync 全 true),缓存接入无副作用。Redis 里的键名结构为「服务端公钥前缀 + 版本构建号 + 限流组 + 客户端 IP」,据此可直接观察各限流组的实际计数。

七、常见问题(实测排障实录)

7.1 健康检查返回 400:time_sync unhealthy

现象:首次部署后健康检查回显如下(HTTP 400,其余两项正常):

{"db_read":{"healthy":true},"db_sync":{"healthy":true},"time_sync":{"healthy":false},"_info":{"ip":"10.89.58.1"}}
HTTP=400

定位:官方安装文档「Installation behind Firewall」章节明示 Psono 依赖对 time.google.com:123(NTP/UDP)的时间同步校验(用于 TOTP、YubiKey、限流与重放保护)。在服务内实测三个 NTP 源连通性:

time.google.com: FAIL TimeoutError timed out
ntp.aliyun.com: OK offset=0.043s server_time=2026-10-24 05:51:51
ntp.ubuntu.com: OK offset=0.303s server_time=2026-10-24 05:51:51

解决方案(二选一):

  1. settings.yaml 增加可达 NTP 源后重启服务(推荐):TIME_SERVER: 'ntp.aliyun.com',实测重启后回显 {"db_read":{"healthy":true},"db_sync":{"healthy":true},"time_sync":{"healthy":true},...}、HTTP 200。
  2. 确认服务器时间本身可靠时,官方允许直接关闭该校验:settings.yaml 设 HEALTHCHECK_TIME_SYNC_ENABLED: False。

7.2 单发管理命令报数据库主机解析失败

现象:用 docker run --rm 单发执行 manage.py createuser 时报错(节选):

django.db.utils.OperationalError: failed to resolve host 'psono-db': [Errno -3] Try again

定位:docker run --rm 单发命令默认不在 compose 定义的网络里,解析不了服务名 psono-db。解决方案:一律改用 docker compose exec psono-server python3 ./psono/manage.py … 在栈内执行;确需单发时给 docker run 显式加 --network psono-753-net 并挂载 settings.yaml。

7.3 非交互终端执行官方命令报 TTY 错误

现象:docker run --rm -ti psono/psono-combo:latest python3 ./psono/generateserverkeys.py 在自动化脚本/远程执行中报:

cannot attach stdin to a TTY-enabled container because stdin is not a terminal

解决方案:去掉 -t(改为 -i 或两者都去掉)。这类一次性命令本就不需要交互终端。

7.4 首启后 30 秒内访问报连接被拒

现象:docker compose up -d 后立刻 curl 8153 端口连接失败。定位:组合镜像启动入口先执行数据库迁移再拉起 Web 服务,psono-server 状态为 health: starting。等待约 30–60 秒后复验即 200。验证手段:docker compose ps 观察 STATUS 变为 (healthy) 后再测端点。

八、参考链接