Joplin 3.7.2 Docker Compose 部署指南

Ubuntu 22.04 上用 Docker Compose 从零部署 Joplin Server 3.7.2 + PostgreSQL 16,覆盖管理员初始化、注册开关、健康验证、持久化、升级运维与客户端同步接入,全程实测。

JoplinJoplinMarkdown2026/10/22

Joplin 3.7.2 Docker Compose 部署指南

目标:在 Ubuntu 22.04 服务器上用 Docker Compose 从零部署 Joplin Server 3.7.2(官方 joplin/server 镜像 + PostgreSQL 16),完成默认管理员初始化、用户注册开关配置、健康检查、数据持久化验证、客户端同步接入验证与升级运维。文中全部命令与回显均在真实部署环境(Ubuntu 22.04、Docker 29.8.1、Docker Compose v5.5.1、Joplin Server 3.7.2,服务地址 http://192.168.146.128:8136)实测通过。

一、环境信息

本篇部署目标与实测环境的关键信息如下,读者按本文操作时请将 IP 换成自己服务器的地址:

  • 操作系统:Ubuntu 22.04.4 LTS(Jammy Jellyfish)
  • Docker:Docker version 29.8.1, build 4a63305
  • Docker Compose:Docker Compose version v5.5.1
  • Joplin Server:joplin/server:3.7.2(Docker Hub 官方镜像,2026-09-07 推送,2026-10-22 实查为最新稳定 tag,latest 与其同源)
  • 数据库:postgres:16 官方镜像
  • 服务端口:容器内监听 22300,对外发布 8136
  • 部署目录:/opt/joplin/(compose 文件与 .env),数据目录 /opt/joplin/postgres/
  • 对外访问地址(APP_BASE_URL):http://192.168.146.128:8136
  • 实测验收链路:部署 → 健康检查 → 管理员登录 → 注册用户 → 终端客户端同步 3 条笔记(291ms 完成)

💡 说明:Joplin 客户端(桌面/移动/CLI)与服务端版本号不同步,服务端镜像 3.7.2 对应的客户端最新为 3.7.x 系列,两端通过同步协议协作,无需版本对齐。Joplin 的定位与选型背景见 《Joplin 介绍与选型指南》。

二、镜像选型说明

Joplin Server 只提供官方 Docker 镜像 joplin/server,没有独立的 deb/rpm 安装包,因此 Docker 是官方推荐的唯一服务端部署方式。镜像 tag 策略(引自镜像内置 README,实测核对 Docker Hub):

tag 形态含义示例
latest始终指向最新发布版latest(2026-10-22 实查与 3.7.2 同一 digest)
beta最新 beta 版3.8.0-beta 等
大版本号锁定大版本3、3-beta
次版本号锁定次版本3.7
补丁版本号精确锁定3.7.2(本文实测采用)

如何选:生产环境锁定精确补丁版本(如 joplin/server:3.7.2),升级由运维显式改 tag 触发,避免 latest 自动漂移引入不可控行为;个人/测试环境可用 latest。

数据库侧两个选择(DB_CLIENT 决定):

数据库适用场景说明
SQLite单机快速试用不配 DB_CLIENT 时默认 SQLite,无需额外容器,官方 README 明确「生产环境应连接数据库」
PostgreSQL生产部署(本文方案)多用户、大容量场景,官方 compose 示例的标准搭配

💡 技术拓展:笔记附件内容(图片、PDF 等)默认存在数据库里(STORAGE_DRIVER 默认 Type=Database),也可切到文件系统或 S3——该参数的完整语义与迁移方法属于配置专题,见 《Joplin 3.7.2 配置参数详解》。本篇部署沿用默认数据库存储,只持久化 PostgreSQL 数据目录即可。

三、前置条件

部署前确认三件事:Docker 已安装、端口未被占用、目录已规划。

1. 安装 Docker Engine 与 Compose 插件(已在官方 apt 仓库安装的情形下验证版本即可):

docker --version

实测回显:

Docker version 29.8.1, build 4a63305
docker compose version

实测回显:

Docker Compose version v5.5.1

若服务器还没有 Docker,按官方文档安装(https://docs.docker.com/engine/install/ubuntu/ ),安装后用上面两条命令验证。

2. 确认对外端口空闲(本文用 8136,可换成任意空闲端口):

ss -ltn | grep 8136 || echo "8136 空闲"

实测回显:

8136 空闲

⚠️ 若回显是 LISTEN ... 形式的占用行,说明端口已被其他进程使用,换一个空闲端口并同步修改后文 .env 与 compose 里的端口。

3. 规划目录:compose 文件放 /opt/joplin/,PostgreSQL 数据目录放 /opt/joplin/postgres/(第二步创建)。

四、部署步骤

本章完成从空目录到可登录服务的完整部署,共 6 步,每步都有实测回显。

4.1 创建部署目录

mkdir -p /opt/joplin && cd /opt/joplin

无输出即成功,可用 pwd 确认当前位置为 /opt/joplin。

4.2 编写 .env 配置文件

创建 /opt/joplin/.env,内容如下(全部为本篇实测值,APP_BASE_URL 中的 IP 换成你的服务器地址):

APP_BASE_URL=http://192.168.146.128:8136
APP_PORT=22300
DB_CLIENT=pg
POSTGRES_PASSWORD=q1w2e3r4
POSTGRES_USER=joplin
POSTGRES_DATABASE=joplin
POSTGRES_PORT=5432
POSTGRES_HOST=db
DEFAULT_ADMIN_PASSWORD=q1w2e3r4
SIGNUP_ENABLED=true

逐项说明:

变量实测值说明
APP_BASE_URLhttp://192.168.146.128:8136服务对外完整地址,客户端同步与 Web 界面都以此为准;带协议与端口,结尾不带斜杠
APP_PORT22300服务在容器内监听的端口,compose 端口映射的目标端口
DB_CLIENTpg数据库类型;pg=PostgreSQL,不设则默认 SQLite
POSTGRES_PASSWORD / POSTGRES_USER / POSTGRES_DATABASEq1w2e3r4 / joplin / joplin数据库凭据,需与下方 db 服务的同名变量一致
POSTGRES_HOST / POSTGRES_PORTdb / 5432数据库地址(compose 服务名)与端口
DEFAULT_ADMIN_PASSWORDq1w2e3r4首次初始化时自动创建的管理员 admin@localhost 的密码;不设置时默认为 admin(实测确认)
SIGNUP_ENABLEDtrue是否开放 /signup 自助注册页;false 时注册接口返回 403(实测确认)

⚠️ APP_BASE_URL 是最常见的坑:客户端与浏览器都通过它访问服务,必须写成「客户端可达」的完整地址。实测中若用 127.0.0.1 访问会直接被拒绝(见 9.5 排障实录)。

4.3 编写 docker-compose.yml

创建 /opt/joplin/docker-compose.yml(在官方 docker-compose.server.yml 形态基础上固化了本篇实测的端口与持久化路径):

name: joplin-3.7.2-stack

services:
  db:
    image: postgres:16
    container_name: joplin-3.7.2-db
    restart: unless-stopped
    volumes:
      - /opt/joplin/postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_DATABASE=${POSTGRES_DATABASE}
    networks:
      - joplin-net

  app:
    image: joplin/server:3.7.2
    container_name: joplin-3.7.2-server
    restart: unless-stopped
    depends_on:
      - db
    ports:
      - "8136:22300"
    environment:
      - APP_BASE_URL=${APP_BASE_URL}
      - APP_PORT=22300
      - DB_CLIENT=pg
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DATABASE=${POSTGRES_DATABASE}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PORT=${POSTGRES_PORT}
      - POSTGRES_HOST=${POSTGRES_HOST}
      - DEFAULT_ADMIN_PASSWORD=${DEFAULT_ADMIN_PASSWORD}
      - SIGNUP_ENABLED=${SIGNUP_ENABLED}
    networks:
      - joplin-net

networks:
  joplin-net:
    driver: bridge

逐项说明:

  • db 服务:PostgreSQL 16;volumes 把数据目录绑定挂载到 /opt/joplin/postgres/,容器删除重建数据不丢(9.4 有实测验证);
  • app 服务:Joplin Server 3.7.2;ports 把容器内 22300 发布到宿主机 8136;depends_on 保证 db 先启动;
  • joplin-net 网络:两个服务必须在同一自定义网络内,app 才能用服务名 db 解析到数据库——实测中若 app 漏配 networks 字段,会落到默认网络导致数据库无法解析(见 9.2 排障实录);
  • name 字段:显式指定 compose 项目名,避免因目录名产生的项目名与其他部署冲突。

⚠️ 环境变量引用注意:compose 的 ${VAR} 是从同目录 .env 读的,但只有出现在 environment: 列表里的变量才会传进容器。实测中曾把 DEFAULT_ADMIN_PASSWORD 只写进 .env 而忘记在 compose 里引用,结果管理员口令仍是默认值——修改后必须重建数据库才会重新初始化(见 9.3)。

4.4 启动服务

cd /opt/joplin && docker compose up -d

实测回显:

[+] Running 33/33
 ✔ Network joplin-372-stack_joplin-net      Created
 ✔ Container joplin-3.7.2-db                Started
 ✔ Container joplin-3.7.2-server            Started

(首次启动会先拉取镜像,回显中多出 Pulling/Pulled 行;网络与容器名的 372 是项目名中点号的转写,属正常现象。)

确认两个服务都处于 Up 状态:

docker compose ps --format '{{.Name}}: {{.Status}}'

实测回显:

joplin-3.7.2-db: Up 5 minutes
joplin-3.7.2-server: Up 2 minutes

4.5 观察首次启动日志(数据库迁移)

首次启动时 Joplin Server 会自动连接数据库并执行建表迁移:

docker compose logs app 2>&1 | grep -E 'Trying to connect|migrat|Latest migration'

实测回显(节选):

joplin-3.7.2-server  | App: Trying to connect to database...
joplin-3.7.2-server  | App: Auto-migrating database...
joplin-3.7.2-server  | App: Latest migration: { name: '20260821120000_token_purpose.js', done: true }

看到 Latest migration ... done: true 即数据库结构初始化完成。日志中还会打印生效的 DB Config,可用于核对配置是否注入成功:

App: DB Config: {
  client: 'pg',
  name: 'joplin',
  ...
  user: 'joplin',
  password: '********',
  port: 5432,
  host: 'db'
}

4.6 健康检查与管理员验证

健康检查接口(Joplin Server 内置 ping 端点):

curl -s http://192.168.146.128:8136/api/ping

实测回显:

{"status":"ok","message":"Joplin Server is running"}

Web 界面:浏览器打开 http://192.168.146.128:8136/,会 302 跳转到登录页:

HTTP/1.1 302
Redirecting to http://192.168.146.128:8136/login.

管理员登录验证(首次初始化自动创建的账号为 admin@localhost,密码取 DEFAULT_ADMIN_PASSWORD):

curl -s -X POST http://192.168.146.128:8136/api/sessions -H 'Content-Type: application/json' -H 'Origin: http://192.168.146.128:8136' -d '{"email":"admin@localhost","password":"q1w2e3r4"}'

实测回显:

{"id":"WLt2JyMWQ9TsL0znW7Sx9v","user_id":"8B8pcmX7JOsEfZoaDArHiw"}

返回 session id 即登录成功。错误的口令会得到 403:

{"error":"Invalid email or password"}

用同一账号登录 Web 登录页(http://192.168.146.128:8136/login ),进入后即 Joplin Server 的笔记浏览界面。

⚠️ 首次初始化后 DEFAULT_ADMIN_PASSWORD 就固定进了数据库,之后改 .env 里的这个变量不会改变已存在管理员的密码(改密码请用 Web 界面的账户设置)。

4.7 开放自助注册并创建普通用户(可选)

SIGNUP_ENABLED=true 时,浏览器访问 http://192.168.146.128:8136/signup 出现注册表单(字段:full_name、email、password、password2)。命令行等价验证:

curl -s -X POST http://192.168.146.128:8136/signup -H 'Origin: http://192.168.146.128:8136' -H 'Content-Type: application/x-www-form-urlencoded' --data 'email=user1%40localhost&full_name=user1&password=Uncommon-Joplin-2026&password2=Uncommon-Joplin-2026' -o /dev/null -w '[HTTP %{http_code}] redirect=%{redirect_url}\n'

实测回显:

[HTTP 302] redirect=http://192.168.146.128:8136/home

新用户登录验证:

curl -s -X POST http://192.168.146.128:8136/api/sessions -H 'Content-Type: application/json' -H 'Origin: http://192.168.146.128:8136' -d '{"email":"user1@localhost","password":"Uncommon-Joplin-2026"}'

实测回显:

{"id":"3decWrD4rc3ExYezdJ7MaC","user_id":"pduOi1NPlZQVR22mC6ZuW6"}

⚠️ 注册接口内置密码强度校验(zxcvbn 规则):实测用演示弱口令 q1w2e3r4 注册返回 422,页面提示「This is a very common password. Add another word or two. Uncommon words are better.」——自助注册必须用足够强的口令(见 9.6)。该强度校验只作用于 signup 流程,DEFAULT_ADMIN_PASSWORD 初始化路径不受它约束。

至此部署完成。若不需要自助注册,建议把 .env 中 SIGNUP_ENABLED 改为 false 并 docker compose up -d 使其生效(实测关闭后注册请求返回 HTTP/1.1 403 Forbidden)。

五、数据持久化说明

Joplin Server 的全部业务数据(账号、笔记本、笔记、附件内容)都存在 PostgreSQL 中;本篇用 bind mount 把数据库目录固定在宿主机 /opt/joplin/postgres/,因此容器删除、重建、升级都不会丢数据。

验证方式(实测):先注册用户并创建笔记,然后 docker compose down + docker compose up -d 整栈重建,再用该用户登录:

cd /opt/joplin && docker compose down && docker compose up -d

实测关键回显:

Container joplin-3.7.2-server  Removed
Container joplin-3.7.2-db      Removed
...
Container joplin-3.7.2-db      Started
Container joplin-3.7.2-server  Started

重建后登录原账号:

curl -s -X POST http://192.168.146.128:8136/api/sessions -H 'Content-Type: application/json' -H 'Origin: http://192.168.146.128:8136' -d '{"email":"user1@localhost","password":"Uncommon-Joplin-2026"}'

实测回显(返回新的 session,账号仍在):

{"id":"BtQ44fHnLNNLtXWg5k7jDj","user_id":"pduOi1NPlZQVR22mC6ZuW6"}

数据库侧也可直接核对(需安装了 psql 客户端的环境):

psql -h 127.0.0.1 -U joplin -d joplin -t -c "select count(*) from users;"

实测回显(2 个用户:管理员 + 注册用户):

     2

💡 附件内容若切换为文件系统存储(STORAGE_DRIVER),还需要额外挂载对应目录,迁移方法见 《Joplin 3.7.2 配置参数详解》。

六、环境变量表

部署可直接使用的完整 .env 变量表(均为本篇实测涉及或镜像内置 README 定义的核心项;服务端全部变量的深度解析见 《Joplin 3.7.2 配置参数详解》):

变量默认值本篇实测值作用
APP_BASE_URL(空)http://192.168.146.128:8136服务对外地址,必配
APP_PORT2230022300容器内监听端口
DB_CLIENTsqlite3pg数据库引擎;pg 为生产推荐
POSTGRES_HOST(空)db数据库主机(compose 服务名)
POSTGRES_PORT54325432数据库端口
POSTGRES_USERjoplinjoplin数据库用户
POSTGRES_PASSWORDjoplinq1w2e3r4数据库密码
POSTGRES_DATABASEjoplinjoplin数据库名
DEFAULT_ADMIN_PASSWORDadminq1w2e3r4初始管理员 admin@localhost 的密码
SIGNUP_ENABLEDfalsetrue/signup 自助注册开关
SQLITE_DATABASE(空)未使用SQLite 模式下的数据库文件全路径
STORAGE_DRIVERType=Database未修改附件内容存储驱动

数据库连接串形态(替代分散的 POSTGRES_* 变量,二选一):POSTGRES_CONNECTION_STRING=postgresql://username:password@your_joplin_postgres_server:5432/joplin。

⚠️ PostgreSQL 的库和用户需要预先存在,Joplin Server 不会代建(镜像内置 README 原文:Ensure that the provided database and user exist as Joplin Server will not create them)。本篇由 postgres:16 镜像的环境变量在建库时一并创建。

七、配置参数说明

compose 文件中值得单独说明的三个配置点:

  1. ports: "8136:22300":左边是宿主机对外端口(可改成 80/443 反代背后的任意端口,但必须与 APP_BASE_URL 一致),右边 22300 是服务容器内固定监听端口,不要改动;
  2. restart: unless-stopped:服务器重启后服务自动拉起(手动 stop 的除外),适合生产;
  3. depends_on:只保证启动顺序,不等待数据库就绪;Joplin Server 自带连接重试,实测中数据库稍慢时日志会出现 db: Could not connect. Will try again. 后自动恢复,无需干预。

反向代理(Nginx/Apache)是可选项:内网直接用 IP:端口即可;公网部署建议加 TLS 反代并把 APP_BASE_URL 改为 https 地址。

八、等价 docker run 参考

不用 Compose 时的等价启动方式(引自镜像内置 README;数据库用 SQLite,适合快速试用):

docker run --env-file .env -p 22300:22300 joplin/server:latest

默认配置下服务器监听 localhost 的 22300 端口并使用 SQLite,无需设置数据库即可启动试用。生产使用时官方明确建议改接 PostgreSQL(即本篇 Compose 方案)。若 PostgreSQL 跑在宿主机上(Linux),README 给出的连通方法是加 --net=host --add-host=host.docker.internal:127.0.0.1,并保持 POSTGRES_HOST 为 localhost 或 127.0.0.1;其他数据库地址则无需处理。

💡 两种形态对比:docker run 单容器适合「一台机器一条命令试用」;生产多用户建议 Compose 双服务(app + postgres),配置、升级、持久化都更可控。本篇主线为 Compose 方案。

九、常用运维命令与排障

9.1 日常运维命令

在 /opt/joplin/ 下执行(实测环境 Docker Compose v5.5.1):

docker compose ps                 # 查看服务状态
docker compose logs -f app        # 跟踪服务日志(Ctrl+C 退出)
docker compose restart app        # 重启 Joplin Server
docker compose stop               # 停止整栈(数据保留)
docker compose up -d              # 启动整栈
docker compose down               # 删除容器与网络(数据卷 /opt/joplin/postgres 保留)

实测 docker compose logs app 的心跳日志形态(服务正常运行时的周期输出,节选):

metrics: Cpu: 6%; Mem: 12953 / 32050 MB (40%); Req: 0 / min; Active req: 0
TaskService: Completed #12 (Process emails) in 9ms

9.2 排障实录一:app 启动报 getaddrinfo EAI_AGAIN db

现象:docker compose up -d 后 server 反复重启,日志刷屏:

db: Could not connect. Will try again. getaddrinfo EAI_AGAIN db

定位:EAI_AGAIN 是 DNS 解析失败——app 服务解析不了数据库主机名 db。检查发现 compose 里 app 服务漏写了 networks: [joplin-net],被挂到了项目默认网络,而 db 在自定义网络,两网隔离。

解决:给 app 服务补上与 db 相同的 networks 引用,重新 docker compose up -d 即恢复。教训:需要互相按服务名访问的服务必须显式挂在同一个自定义网络。

9.3 排障实录二:DEFAULT_ADMIN_PASSWORD 不生效

现象:.env 设置了 DEFAULT_ADMIN_PASSWORD=q1w2e3r4,重建后 admin@localhost + q1w2e3r4 仍登录失败(Invalid email or password),旧口令 admin 反而能登录。

定位:该变量写进了 .env 但 compose 的 environment: 列表没有引用它,容器内根本没收到(用 docker exec <容器> printenv | grep DEFAULT 可核实)。

解决:在 compose 的 app 服务 environment: 里补 - DEFAULT_ADMIN_PASSWORD=${DEFAULT_ADMIN_PASSWORD};由于该变量只在数据库首次初始化时读取,已初始化过的环境还要清空数据目录 /opt/joplin/postgres/ 后整栈重建(仅测试环境可行),再 docker compose restart app 触发迁移。

相关实测:清库重建后若只重启了数据库而没重启 app,登录接口会报 relation "users" does not exist(错误码 42P01)——这是 app 尚未在新库上执行迁移,docker compose restart app 后自动迁移完成即可正常登录。

9.4 排障实录三:端口被占用

现象:docker compose up -d 报:

Error response from daemon: ... Bind for 0.0.0.0:8135 failed: port is already allocated

定位与解决:宿主机 8135 已被其他服务监听。用 ss -ltnp | grep 8135 确认占用方,换一个空闲端口(本篇实测从 8135 改为 8136),同步修改 compose 的 ports 与 .env 的 APP_BASE_URL 后重新 up -d。

9.5 排障实录四:Invalid origin

现象:服务明明健康,但 curl http://127.0.0.1:8136/api/ping 返回:

Invalid origin: http://127.0.0.1:8136

定位:Joplin Server 校验请求来源,只接受 APP_BASE_URL 指定的地址形态。

解决:访问时使用 .env 里配置的完整地址 http://192.168.146.128:8136(即 APP_BASE_URL)。客户端同步配置同样必须填这个地址。若要更换访问形态(如加域名),先改 APP_BASE_URL 再重启服务。

9.6 排障实录五:注册返回 422

现象:POST /signup 返回 422 Unprocessable Entity,页面提示「This is a very common password...」。

定位:signup 流程内置 zxcvbn 密码强度校验,弱口令直接拒绝。

解决:自助注册改用高强度口令(多词组合+符号+数字,实测 Uncommon-Joplin-2026 通过)。注意这与 DEFAULT_ADMIN_PASSWORD 路径不同——后者不做强度校验,但仍应设置强口令。

9.7 升级运维

升级固定三步(以升级到未来新版本 tag 为例,本篇实测当前 latest 与 3.7.2 同源,pull 无新层):

# 1. 修改 docker-compose.yml 中 image 为目标版本,如 joplin/server:3.8
# 2. 拉取新镜像
docker compose pull

实测回显:

 Image joplin/server:3.7.2 Pulling
 Image postgres:16 Pulling
 Image joplin/server:3.7.2 Pulled
 Image postgres:16 Pulled
# 3. 滚动重建
docker compose up -d

升级后验证健康检查与登录(同 4.6),并观察启动日志确认迁移完成(Latest migration ... done: true)。回滚:把 image tag 改回旧版本号执行 docker compose up -d 即可,数据目录不变(若新版本已执行数据库正向迁移,跨版本回滚前应先恢复 /opt/joplin/postgres/ 的备份)。

9.8 客户端同步接入验证

部署的最终目的是给客户端当同步目标。任一客户端的配置路径:工具 → 选项 → 同步 → 同步目标选「Joplin Server」,填三项:Server 地址 = APP_BASE_URL(实测 http://192.168.146.128:8136)、邮箱 = admin@localhost、密码 = 管理员密码,保存后点「同步」。

本篇用 Joplin 终端客户端完成实测(安装与命令详见 《Joplin 3.7.2 常用 CLI 命令与工具详解》):

joplin config sync.target 9
joplin config sync.9.path http://192.168.146.128:8136
joplin config sync.9.username admin@localhost
joplin config sync.9.password q1w2e3r4
joplin mkbook Work
joplin mknote '同步机制实测笔记'
joplin sync

实测同步回显(首次同步,3 条条目上载完成):

Synchronisation target:  (9)
Starting synchronisation...
Created remote items: 1.
Downloading resources...
Created remote items: 3. Fetched items: 3/3. Completed: 21/10/2026 18:02 (291ms)

服务端数据库侧核对(items 表已有同步上来的条目):

psql -h 127.0.0.1 -U joplin -d joplin -t -c "select count(*) from items;"

实测回显:

     6

同步成功即服务端部署全链路(部署 → 管理员 → 注册 → 客户端 → 数据落库)验收完毕。日常使用中的配置项调优见 《Joplin 3.7.2 配置参数详解》。

十、参考链接