自托管 Todoist 替代 Conatus:能导入现有任务,docker-compose 就能跑

简介

什么是 Conatus?

Conatus 是一个开源的自托管任务管理器,适用于项目、循环任务、提醒和团队协作,灵感源自 Todoist。它可以帮助用户在一个安全可控的环境中,以列表、看板或日历视图组织日常任务和个人项目,数据完全掌握在自己手中。

主要特点

  • 项目与组织:支持通过项目、分区、标签和优先级来组织任务,让工作结构清晰。
  • 循环任务与提醒:可创建循环任务,设置截止日期、到期时间和提醒,支持直接在任务名称中输入日期、时间、时长和重复规则。
  • 协作与共享:通过评论、文件附件和共享项目实现多人协作,支持日历、列表或看板视图切换。
  • Todoist 数据导入:支持直接导入 Todoist 的任务数据,降低迁移成本。
  • API 与自动化:提供版本化 REST API(/api/v1)和 Webhooks 事件通知,可通过 API token 集成第三方工具。
  • MCP 支持:提供独立的 MCP 服务器,方便 AI 智能体直接管理任务。
  • 开源免费:基于 AGPL-3.0 协议开源,可免费使用和修改。

应用场景

  • 个人任务管理:个人用户可用它替代 Todoist,管理和跟踪日常待办、习惯与项目进度。
  • 家庭共享清单:家庭成员可通过共享项目协作制定购物清单、家庭活动计划等。
  • 团队项目管理:团队可通过评论、文件附件和看板视图管理迭代任务,配合循环任务跟踪重复性工作。
  • 自动化与 AI 集成:开发者可通过 REST API、Webhooks 和 MCP 协议接入 CI 流水线、通知机器人或 AI 助手。
  • 开发者测试:开发人员可以快速搭建测试环境,体验完整的任务管理 API 和前端功能。

Conatus 项目视图

Conatus 是一个功能全面、注重隐私的自托管 Todoist 替代品,它把任务、循环工作和协作整合到一个你完全掌控的系统中。提供完整的 Web 端和 REST API,并支持通过 MCP 协议接入 AI 助手,让自动化管理任务成为可能。

安装

在群晖上以 Docker 方式安装。

本文写作时, latest 版本对应为 1.0.0;

Conatus 采用多容器架构部署,由 db(PostgreSQL)、minio(对象存储)、migrate、bootstrap(初始化和引导)、app(主应用)以及可选的 backup、mcp 等服务组成,建议使用 docker-compose 进行部署,可以自动处理服务间的依赖关系。

docker-compose 安装

1. 下载 Compose 文件和环境模板

首先创建目录并下载官方提供的 docker-compose.yml 和 .env.example:

1
2
3
4
5
6
7
8
9
# 新建文件夹 conatus 和 子目录
mkdir -p /volume1/docker/conatus/{backups,data,mcp,minio}

# 进入 conatus 目录
cd /volume1/docker/conatus

# 下载
curl -O https://raw.githubusercontent.com/nojusmorkunas/conatus/main/docker-compose.yml
curl -o env.txt https://raw.githubusercontent.com/nojusmorkunas/conatus/main/.env.example

也可以不用下载

直接复制老苏的 env.txt 和 docker-compose.yml

2. 编辑环境变量

以下为完整的 env.txt (含中文注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# Compose 部署配置。
# CONATUS_VERSION 必须匹配一个已经发布的镜像版本标签,
# 不要包含前面的 "v"。
# 例如:GitHub Release v1.0.0 对应填写 1.0.0。
CONATUS_VERSION=latest

# Web 应用的宿主机端口。
# 容器内部监听端口为 3000。
CONATUS_PORT=4399

# MCP 服务由 github.com/nojusmorkunas/conatus-mcp
# 独立发布,发布时间和主程序不同,
# 因此 MCP 版本单独管理。
CONATUS_MCP_VERSION=latest

# 仅当从 fork 项目或其他镜像仓库部署时才需要修改这些值。
# CONATUS_IMAGE=ghcr.io/nojusmorkunas/conatus
# CONATUS_MCP_IMAGE=ghcr.io/nojusmorkunas/conatus-mcp

POSTGRES_USER=app
POSTGRES_PASSWORD=replace-with-a-long-url-safe-password
POSTGRES_DB=app

# 当直接使用 npm 运行应用时使用。
# Docker Compose 会根据上面的 POSTGRES_* 配置,
# 自动生成内部数据库连接地址。
DATABASE_URL=postgres://app:replace-with-a-long-url-safe-password@localhost:5432/app

# 当使用域名或反向代理时,
# 两个值都应该设置为外部 HTTPS 地址。
# AUTH_URL=https://tasks.example.com
# PUBLIC_BASE_URL=https://tasks.example.com

# 身份认证密钥。使用 npx-auth-secret 命令生成。
# 老苏是用 penssl rand -hex 32 生成的
AUTH_SECRET=generate-with-npx-auth-secret

# 可选:一次性管理员初始化。
# 在第一次执行 `docker compose up` 前设置这两个值。
# 当数据库为空时,该任务会创建一个已验证的管理员账号。
# 第一次登录成功后,删除这两个配置,
# 并删除已经停止的 bootstrap 容器。
CONATUS_ADMIN_USERNAME=admin
CONATUS_ADMIN_PASSWORD=replace-with-a-long-random-password

# 默认情况下,注册模式为邀请制。
# 第一个注册账户会自动初始化服务器并成为管理员。
REGISTRATION_MODE=invite-only

# 仅用于本地开发环境。
# 设置为 1 后:
# - 禁用登录、注册、密码重置、邮件重新发送、错误报告等功能的频率限制;
# - 开放所有人注册。
#
# 注意:
# 该配置不会根据 NODE_ENV 判断环境,
# 因此不要在公网可访问的服务器上启用。
# CONATUS_DEV_MODE=1

# S3 对象存储配置,用于附件存储。
S3_ENDPOINT=localhost
S3_PORT=9000
S3_ACCESS_KEY=replace-with-a-random-access-key
S3_SECRET_KEY=replace-with-a-long-random-secret-key
S3_BUCKET=attachments

# 邮件功能默认关闭。
# 只有以下三个配置全部填写后才会启用邮件功能。
# SMTP_HOST=mail.example.com
# SMTP_PORT=587
# SMTP_FROM="Conatus <[email protected]>"

# 可选:接收 JSON 格式错误报告的远程接口地址。
# ERROR_WEBHOOK_URL=

# 可选:远程 MCP sidecar(MCP 辅助服务)。
# 使用以下命令启动:
# docker compose --profile mcp up --build
#
# 在 Settings(设置)中创建 TASKS_API_TOKEN。
# 对于支持通过 URL 连接的客户端,推荐使用 OAuth。
# TASKS_API_TOKEN=tdm_replace_me
# MCP_PUBLIC_URL=https://mcp.example.com/mcp
# MCP_OAUTH_PASSWORD=generate-a-separate-long-random-password
# MCP_ALLOWED_ORIGINS=https://your-ai-host.example

# 静态备用配置:
# 当客户端无法使用 OAuth 时,
# 可以使用固定 Bearer Token 进行认证。
# MCP_BEARER_TOKEN=generate-a-long-random-token

增加了中文注释,记得要采用 UTF-8 格式保存

只需要修改以下关键配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 使用 latest 表示当前稳定版,或指定版本号如 1.0.0
CONATUS_VERSION=1.0.0
# Web 界面对外端口,容器内部监听 3000
CONATUS_PORT=4399

# 如果改了参数,DATABASE_URL 也要相应的调整
POSTGRES_USER=app
POSTGRES_PASSWORD=conatus123
POSTGRES_DB=app

# 认证密钥,建议用较长随机字符串
AUTH_SECRET=replace-with-a-long-random-secret

# 对象存储 S3 凭据(MinIO)
S3_ACCESS_KEY=7e609d7743b225544d4bc29877490b6b1c958709299d163ee9e9c6705682df26
S3_SECRET_KEY=f7c61b439806e8b504abe8ef4b4be06b3710bafd98ab184a53846eb408a46b6e

# 可选:在空数据库上创建首个管理员
CONATUS_ADMIN_USERNAME=admin
CONATUS_ADMIN_PASSWORD=laosu123

环境变量说明:

  • POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB:用于配置 PostgreSQL 数据库的账号、密码和库名,请使用强密码。
  • S3_ACCESS_KEY / S3_SECRET_KEY:MinIO 对象存储的凭据,用于存放附件的持久化数据。
  • CONATUS_ADMIN_USERNAME / CONATUS_ADMIN_PASSWORD:首次在空数据库部署时用于初始化管理员账号,登录后应将其移除。

如果使用了域名或反向代理,还需要设置 AUTH_URL 和 PUBLIC_BASE_URL 为外部 HTTPS 地址。

3. 启动 Conatus

以下为完整的 docker-compose.yml 核心配置示例(含中文注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
services:
db:
image: postgres:17-alpine
container_name: conatus-db
restart: unless-stopped
# ports:
# - "127.0.0.1:${POSTGRES_PORT:-5432}:5432"
volumes:
- ./data:/var/lib/postgresql/data # 数据库数据持久化
environment:
POSTGRES_USER: ${POSTGRES_USER:-app}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_DB: ${POSTGRES_DB:-app}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""]
interval: 5s
timeout: 5s
retries: 10
start_period: 10s

minio:
# MinIO stopped publishing to Docker Hub, so minio/minio no longer pulls.
# Pinned because the console this file exposes on 9001 was dropped from
# later releases.
# image: quay.io/minio/minio:RELEASE.2025-04-22T22-12-26Z
image: chrislusf/seaweedfs:latest
container_name: conatus-minio
restart: unless-stopped
# ports:
# - "127.0.0.1:${MINIO_PORT:-9000}:9000"
# - "127.0.0.1:${MINIO_CONSOLE_PORT:-9001}:9001"
volumes:
- ./minio:/data # 对象存储数据持久化
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY:-minioadmin}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY:-minioadmin}
command: server /data --console-address ":9001"

mailpit:
profiles: ["email-test"]
image: axllent/mailpit
container_name: conatus-mailpit
restart: unless-stopped
ports:
- "1025:1025"
- "8025:8025"

backup:
image: postgres:17-alpine
container_name: conatus-backup
restart: unless-stopped
volumes:
- ./backups:/backups # 备份数据持久化
depends_on:
db:
condition: service_healthy
environment:
PGHOST: db
PGUSER: ${POSTGRES_USER:-app}
PGPASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
PGDATABASE: ${POSTGRES_DB:-app}
BACKUP_INTERVAL: ${BACKUP_INTERVAL:-86400} # 备份间隔,默认每日一次
BACKUP_KEEP: ${BACKUP_KEEP:-7} # 保留备份份数,默认 7
entrypoint: ["sh", "-c"]
command:
- |
while true; do
pg_dump -Fc -f "/backups/app-$$(date -u +%Y%m%dT%H%M%SZ).dump"
ls -1t /backups/app-*.dump | tail -n +$$((BACKUP_KEEP + 1)) | xargs -r rm --
sleep "$$BACKUP_INTERVAL"
done

migrate:
image: ${CONATUS_IMAGE:-ghcr.io/nojusmorkunas/conatus}:${CONATUS_VERSION:-latest}-ops
container_name: conatus-migrate
command: ["npm", "run", "migrate"]
restart: "no"
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@db:5432/${POSTGRES_DB:-app}

bootstrap:
image: ${CONATUS_IMAGE:-ghcr.io/nojusmorkunas/conatus}:${CONATUS_VERSION:-latest}-ops
container_name: conatus-bootstrap
command: ["npm", "run", "bootstrap"]
restart: "no"
depends_on:
- migrate
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@db:5432/${POSTGRES_DB:-app}
CONATUS_ADMIN_USERNAME: ${CONATUS_ADMIN_USERNAME:-}
CONATUS_ADMIN_PASSWORD: ${CONATUS_ADMIN_PASSWORD:-}

app:
image: ${CONATUS_IMAGE:-ghcr.io/nojusmorkunas/conatus}:${CONATUS_VERSION:-latest}
container_name: conatus-app
restart: unless-stopped
depends_on:
- db
- migrate
- bootstrap
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@db:5432/${POSTGRES_DB:-app}
AUTH_SECRET: ${AUTH_SECRET:?set AUTH_SECRET in .env}
AUTH_URL: ${AUTH_URL:-}
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-}
REGISTRATION_MODE: ${REGISTRATION_MODE:-invite-only}
# Local development only: disables rate limiting and opens registration.
CONATUS_DEV_MODE: ${CONATUS_DEV_MODE:-0}
S3_ENDPOINT: minio
S3_PORT: 9000
S3_ACCESS_KEY: ${S3_ACCESS_KEY:?set S3_ACCESS_KEY in .env}
S3_SECRET_KEY: ${S3_SECRET_KEY:?set S3_SECRET_KEY in .env}
S3_BUCKET: ${S3_BUCKET:-attachments}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-}
SMTP_FROM: ${SMTP_FROM:-}
ERROR_WEBHOOK_URL: ${ERROR_WEBHOOK_URL:-}
ports:
- "${CONATUS_BIND_ADDRESS:-0.0.0.0}:${CONATUS_PORT:-4399}:3000"

# Optional per-user MCP proxy. Start with: docker compose --profile mcp up -d
# Create a scoped tdm_ token in Settings before enabling this profile.
mcp:
profiles: ["mcp"]
image: ${CONATUS_MCP_IMAGE:-ghcr.io/nojusmorkunas/conatus-mcp}:${CONATUS_MCP_VERSION:-latest}
container_name: conatus-mcp
restart: unless-stopped
depends_on:
app:
condition: service_healthy
environment:
TASKS_BASE_URL: http://app:3000
TASKS_API_TOKEN: ${TASKS_API_TOKEN:-tdm_configure_a_real_token_before_starting}
MCP_HOST: 0.0.0.0
MCP_PORT: 4398
MCP_PUBLIC_URL: ${MCP_PUBLIC_URL:-}
MCP_OAUTH_PASSWORD: ${MCP_OAUTH_PASSWORD:-}
MCP_OAUTH_STORE_PATH: /data/oauth-store.json
MCP_BEARER_TOKEN: ${MCP_BEARER_TOKEN:-}
MCP_ALLOWED_ORIGINS: ${MCP_ALLOWED_ORIGINS:-http://localhost:4399}
ports:
- "4398:3001"
volumes:
- ./mcp:/data
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4398/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s

可选 MCP 侧车服务(用于接入 AI 助手):在 env.txt 中设置 token 后,运行 docker compose --profile mcp up -d 可启用。

1
2
3
4
5
6
7
8
9
10
11
12
AI 助手
│
│ MCP
▼
┌──────────────┐
│ MCP 侧车服务 │
└──────┬───────┘
│
▼
┌──────────────┐
│ 主应用 │
└──────────────┘

执行下面的命令:

1
2
# 一键启动
docker-compose --env-file env.txt up -d

启动时需要拉取 Conatus 应用、操作工具、PostgreSQL 和 MinIO 镜像,完成后访问 http://<IP>:4399 即可。

运行

在浏览器中访问 http://<群晖IP>:4399 即可进入登录界面。

使用 env.txt 中配置的 CONATUS_ADMIN_USERNAME / CONATUS_ADMIN_PASSWORD 登录管理员账号

登录后,从 env.txt 中移除 CONATUS_ADMIN_USERNAME 和 CONATUS_ADMIN_PASSWORD 这两项配置

默认支持两种方式导入 Todoist 的数据,使用 Todoist 的 API Token 或者备份的数据,但老苏没用过 Todoist,只能自己新建

先建 Project

再建 Task

可以根据需要添加需要的描述、优先级、重复、提醒、附件等

API 调用

从 Preferences –> API tokens

创建 API Token

调用 REST 接口获取尚未完成的任务列表

1
curl -H "Authorization: Bearer tdm_P9i382EHuTTWpAbUEc8wGJDxaNcQC0Zu" "http://192.168.0.197:4399/api/v1/tasks?completed=false"

MCP

默认情况下,MCP服务并没有启动,如果需要使用,需要先启用 env.txt 中的相关参数,然后再执行

1
2
# 启动 mcp 服务
docker-compose --env-file env.txt --profile mcp up -d

如果一切正常,应该能看到 conatus-mcp 服务

但是老苏没搞定,从日志看需要 Issuer URL must be HTTPS,但老苏手上的 freenom 域名都过期了

注意事项

  1. 数据库备份:升级前务必先备份数据库,某些数据库迁移需要恢复才能安全回滚。可使用内置 backup 服务自动备份,默认每日执行并保留最新 7 份。
  2. 初始管理员:只有在数据库为空时 Bootstrap 进程才会创建管理员,首次登录后请及时移除 env.txt 中的相应配置。
  3. 端口冲突:默认宿主端口为 4399,可通过修改 CONATUS_PORT 来更换;数据库、MinIO 没有对外暴露端口。
  4. 反向代理与 HTTPS:若使用域名或反向代理,必须设置 AUTH_URL 和 PUBLIC_BASE_URL 为外部 HTTPS 地址。
  5. 安全建议:默认注册模式为邀请制(invite-only),请使用强密码,并妥善保管 API Token(仅在创建时显示一次)。切勿在生产环境中启用 CONATUS_DEV_MODE=1,它会关闭限流并开放注册。

参考文档

nojusmorkunas/conatus: A self-hosted task manager for projects, recurring work, reminders, collaboration and automation. Inspired by Todoist.
地址:https://github.com/nojusmorkunas/conatus

Conatus 官方主页
地址:https://useconatus.com

nojusmorkunas/conatus-mcp: Conatus 的 MCP 服务器,提供 stdio 与远程 Streamable HTTP 传输,供 MCP 兼容的 AI 智能体管理任务
地址:https://github.com/nojusmorkunas/conatus-mcp