用 Docker 在 VPS 上部署 Vikunja:把项目任务、附件和截止日期收在一起

用 Docker 和 PostgreSQL 部署 Vikunja,整理任务、附件、日期与共享权限,完成成组备份和恢复检查。

·10 min工作流

准备一次网站迁移,事项散落在聊天里:有人提醒导出数据库,有人补一句域名还没改,还有人把验收截图发到另一处。迁移结束后再追溯,很难确定哪件事已经做过,哪件事只是讨论过。Vikunja 可以把这些事项放进项目,用任务、负责人、日期和附件留下可查询的进度。

本文部署一个小团队或个人项目使用的实例。先把账号、数据库和附件安排好,再建立一个能从准备走到验收的项目。任务中先写清楚要交付什么、由谁确认完成,再用日历安排日期、用看板查看进度。

选一个实际项目做第一轮整理

Vikunja 提供任务列表、看板、日历等视图,并支持共享和任务附件。它适合维护一个跨几天或几周的项目,也能放个人待办。多人进入以前,先明确项目的访问范围,查看分享方式;知道链接和拥有项目账号权限不一定是同一回事。

第一次可以建“网站迁移”项目,分出准备、迁移、验收、收尾几组任务。任务写成具体结果,例如“备份导出并在临时数据库验证恢复”,比“处理数据库”容易验收。把备份文件的存放位置记在任务里,实际备份和密码仍放在有权限控制的存储中。

截止日期留给真正需要在某天完成的事项,不必给所有任务都设为今天。某项任务依赖域名解析、另一项依赖数据复制,描述里要写明前置条件。看板上的“进行中”可以展示当前安排,但不能代替维护窗口和回退条件。

数据库和附件是两份数据

下面采用项目 Docker 部署文档提供的 PostgreSQL 方式。数据库保存账号、项目和任务等记录,附件保存到应用文件目录。备份只有数据库而没有附件,恢复后可能看到任务里还有文件名,点击却拿不到原文件。

服务器需要 Docker Engine 和 Compose 插件,当前用户有 Docker 权限。Caddy 在宿主机提供 HTTPS,域名 tasks.example.com 已解析到 VPS。数据库不映射公网端口,应用只监听宿主机回环地址。

mkdir -p /opt/vikunja/files /opt/vikunja/db
cd /opt/vikunja
sudo chown 1000:1000 files
umask 077
openssl rand -hex 32
openssl rand -hex 32

分别将两个随机值写进 .env:一个作为数据库密码,一个作为服务 secret,不要使用相同值。附件目录所有者采用官方容器要求的 UID 1000;如果选择另一个镜像或修改运行用户,权限也要对应调整。数据库目录由 PostgreSQL 容器初始化,不要把它随手改成应用用户的所有权。

VIKUNJA_DB_PASSWORD=your-generated-database-password
VIKUNJA_SERVICE_SECRET=your-generated-service-secret
VIKUNJA_PUBLIC_URL=http://localhost:13456/
chmod 600 .env

当前配置使用 VIKUNJA_SERVICE_SECRET。旧教程中的 JWTSecret 字段已被标记为弃用,不要在新实例中继续堆叠旧参数。固定服务 secret 可以让重启前后使用同一密钥,避免每次启动都重新生成;它与用户登录密码仍是不同内容。

启动两个服务

保存为 compose.yaml。下面使用官方示例当前列出的 PostgreSQL 18;它的数据挂载目录是 /var/lib/postgresql。旧 PostgreSQL 版本的目录安排不同,不要用这份 Compose 直接覆盖现有数据库,也不要通过更换大版本镜像完成升级。

services:
  vikunja:
    image: vikunja/vikunja:latest
    restart: unless-stopped
    ports:
      - "127.0.0.1:3456:3456"
    environment:
      VIKUNJA_SERVICE_PUBLICURL: ${VIKUNJA_PUBLIC_URL:?set public url}
      VIKUNJA_SERVICE_SECRET: ${VIKUNJA_SERVICE_SECRET:?set service secret}
      VIKUNJA_SERVICE_TIMEZONE: Asia/Shanghai
      VIKUNJA_SERVICE_ENABLEREGISTRATION: "true"
      VIKUNJA_DATABASE_TYPE: postgres
      VIKUNJA_DATABASE_HOST: db
      VIKUNJA_DATABASE_USER: vikunja
      VIKUNJA_DATABASE_PASSWORD: ${VIKUNJA_DB_PASSWORD:?set database password}
      VIKUNJA_DATABASE_DATABASE: vikunja
    volumes:
      - ./files:/app/vikunja/files
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:18
    restart: unless-stopped
    environment:
      POSTGRES_USER: vikunja
      POSTGRES_DB: vikunja
      POSTGRES_PASSWORD: ${VIKUNJA_DB_PASSWORD:?set database password}
    volumes:
      - ./db:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h localhost -U $$POSTGRES_USER -d $$POSTGRES_DB"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 30s

本例为了首次创建账号暂时允许注册,此时入口还未公开。应用镜像的 latest 用于首次选择当前版本,检查通过后应固定实际摘要或正式标签;数据库镜像也需要记录确切版本。更新容器前先读升级说明,尤其是数据库迁移和附件处理变化。

docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 vikunja db
curl -I http://127.0.0.1:3456/

config --quiet 避免把机密展开到终端输出。数据库首次初始化可能需要时间,应用通过健康依赖等待它可连接。若一直失败,检查数据库日志和用户名,不要只重启应用。现有数据库已经初始化以后,修改 .env 里的密码不会自动修改库内用户密码,需要正式的数据库密码变更流程。

先建自己的账号,再接公共域名

在自己的电脑通过 SSH 转发访问初始化入口,替换 SSH 用户和服务器地址,打开本机 13456 端口创建账号。

ssh -N -L 13456:127.0.0.1:3456 [email protected]

初始化期间,.env 中的 public URL 与本机转发入口一致。账号建立以后,把 VIKUNJA_PUBLIC_URL 改为 https://tasks.example.com/,接好域名代理再重新登录。 需要使用的账号建立完成后,把 VIKUNJA_SERVICE_ENABLEREGISTRATION 改为 "false",运行 docker compose up -d 应用变更。注册是否关闭,要用未登录窗口实际检查。只有关掉页面上的一个入口,而接口仍能创建用户,不能算完成限制。

宿主机 Caddy 增加以下站点段,验证配置后重载。代理在容器里时改用共享 Docker 网络上的应用地址。

tasks.example.com {
    reverse_proxy 127.0.0.1:3456
}

在正式域名下重新登录,建立一个测试项目,添加任务、上传小附件,再下载核对。附件上传出现权限错误时,先查 files 的所有者和写入权限。页面打开但附件保存失败,通常不需要重新安装整个服务。

把项目安排成可交接的任务

为迁移项目建几个明确任务:“导出当前数据库”“验证备份恢复”“复制附件”“配置新站点入口”“核对公开页面”。每个任务描述输入材料、完成标准和对应负责人。负责人临时不在时,其他成员打开任务就能继续,不需要先翻完聊天记录。

给任务附一张验收截图时,说明截图对应哪个域名、什么时间和哪个环境。只有截图没有入口,下一次讨论仍会弄不清它来自测试还是正式站点。附件里避免包含登录令牌、管理页面账号和完整环境文件;这类资料一旦进入共享项目,访问范围会随项目成员变化。

看板列可以先用待办、进行中、待验收、完成。完成并非“我已经执行命令”,而是交付结果已经检查过,例如公开页面能够访问、文件下载内容正确、回退材料仍在。任务越具体,状态越容易保持可信。

需要按日期安排的任务切到日历查看,检查时区和截止时间。跨地区协作时在描述中写清维护窗口所属时区,避免大家只看到一个日期。客户端的展示时间与服务配置都要检查,容器设置时区并不能替代一次实际日期验证。

共享权限用第二个账号验收

创建成员账号后,用它登录检查项目是否可见、能否修改任务和下载附件。不要一直用管理员视角判断所有人看到什么。共享给个人、团队或链接时,分别核对所选权限和实际行为,尤其是匿名访问范围。

项目要对外展示时,用未登录窗口打开分享入口,确认没有带出内部项目、成员信息或私人附件。协作者离开以后,撤销相应权限,并检查过去建立的分享链接是否仍然有效。删除成员与撤销公开链接可能属于不同操作,不能只做一项就认为全部访问已收回。

通知和邮件需要另外配置 SMTP,再验证真实的提醒或账号恢复流程。任务里有截止日期,并不自动等于手机一定收到提醒;发件配置、用户设置和客户端权限都可能影响结果。先用一个短期测试任务核对,再让团队依赖它。

备份要让任务和附件同时停在一个时间点

短暂停止应用后,数据库仍可供备份工具访问,此时不会再有应用写入。用 pg_dump 生成数据库备份,再打包附件与配置,最后启动应用。执行前准备备份目录并检查剩余空间;任一步失败都要检查原因和服务是否已经恢复。

cd /opt/vikunja
mkdir -p /opt/backups/vikunja
docker compose stop vikunja
docker compose exec -T db pg_dump -U vikunja -d vikunja -Fc > /opt/backups/vikunja/database.dump
tar -czf /opt/backups/vikunja/files-and-config.tgz compose.yaml .env files
docker compose start vikunja

这个示例使用固定文件名,正式使用应为每次备份建立日期目录,成功以后再清理过期副本,不要先覆盖唯一能恢复的一份。数据库 dump 与附件压缩包应作为同一组保存,复制到另一处存储,并限制读取。配置备份里包含服务 secret 和数据库密码。

恢复测试在独立环境中完成,先建立空数据库,再用 PostgreSQL 恢复工具导入,与同一组附件和配置配套。选择备份时对应的应用版本,修改测试入口,暂时不要接正式通知和域名。登录后检查项目、任务状态、成员权限,再下载几个不同日期的附件。

正式升级前取得新备份,读发布说明,再更新应用。若新版迁移了数据库,回退需要旧应用和升级前数据库、附件一起恢复。只把镜像标签改回去,并不能保证旧版本理解新的数据库格式。

项目结束后,把已确认的任务归档,留下验收入口和必要附件,删除无用的临时分享。以后做相似迁移,可以参考这份已经走完的任务记录,知道哪些步骤确实完成过,也知道当时怎样判断结果。