
简介
什么是 Windshift?
Windshift是一个开源的自托管工作管理平台,目标是把Jira的深度保留下来,同时去掉Jira的沉重。它用一个Go二进制文件打包了Svelte前端,默认使用内嵌SQLite,也可以换成PostgreSQL。
对于受够了 Jira 每年涨价、插件生态层层收费,又不想退到 Trello 那种只能看板的团队,Windshift 提供了一个折中方案:工作项、Epic、Sprint、自定义工作流、自动化、服务台、SLA、知识库、测试管理,全部内置。
主要特点
- Jira 级的规划能力 :看板、
Backlog、迭代、Epic与子任务、层级树、路线图、仪表盘一应俱全,支持工作项类型的自由配置 - 每个工作区独立配置:工作项类型、自定义字段、界面、状态、工作流、优先级、权限都按工作区隔离,互不干扰
- JQL 风格查询语言 :内置
Windshift Query Language,过滤条件可以保存为共享集合(Collection),驱动看板、列表、日历、路线图 - 事件驱动自动化 :规则可以在事件发生时改字段、发通知、创建工作项,不需要再买
Marketplace上的自动化插件 - 服务台一体化 :SLA 指标与目标、队列视图、预设回复、审批、客户门户全部内置,无需单独部署
Jira Service Management - 替代周边工具 :知识库页面顶掉
Confluence,测试用例/测试运行顶掉Xray、Zephyr,资产CMDB顶掉Insight,时间跟踪也是原生的 - Jira 导入器 :内置迁移工具,支持
Jira Cloud与Data Center的项目、评论、附件、worklog、看板、Sprint、自定义字段导入 - 九种语言 :含简体中文、阿拉伯语(完整
RTL镜像布局)、日韩俄德法西葡 - 单二进制、无外部依赖 :不需要
Java,不需要Elasticsearch集群,约70 MB一个文件,树莓派也能跑 - 开源免费 :基于
AGPL-3.0协议开源,无用户数限制,无需License Key
应用场景
- 团队从 Jira 迁移:受够了按人头计费与插件订阅,用内置导入器把项目整体搬过来,数据留在自己服务器上
- 中小团队自建项目管理 :五到五十人的团队,不想维护
Confluence + Jira + Xray + Insight这一套,一个容器全解决 - 外包与客户支持 :客户门户 +
SLA+ 队列,对外提供工单入口,对内用同一套工作项跟踪进度 - 研发流程闭环 :关联
GitHub、GitLab、Gitea、Forgejo,分支和PR直接绑到工作项上,提交记录自动回流 - 内网/合规环境:完全离线可用,数据不出机房,适合对数据主权有要求的场景

Windshift 是一个把 Jira 核心能力和周边插件压缩进单个容器的自托管方案,适合想拿回数据控制权、又不愿意在功能上做减法的团队。
安装
官方镜像只发布在 ghcr.io,群晖的 Docker 套件搜不到,需要用命令行拉取。
本文写作时,最新稳定版本为
v0.9.1(2026-10-21发布),老苏选择了最新的main

准备工作
通过 SSH 登录到您的群晖,执行下面的命令,先创建数据目录,并生成一个固定的密钥:
bash
# 新建文件夹 windshift 和 子目录
mkdir -p /volume1/docker/windshift/{data,pgdata}
# 进入 windshift 目录
cd /volume1/docker/windshift
# 生成 SSO_SECRET,只生成一次,之后保持不变
openssl rand -hex 32
SSO_SECRET用于签名会话Cookie和加密SSO凭据。一旦更换,所有人会被踢下线,已保存的SSO/集成凭据也会失效。
env.txt
配置文件主要就下面几个参数需要调整
SERVER_IP:服务器IPPORT:主机端口SSO_SECRET:上一步中openssl rand -hex 32生成POSTGRES_PASSWORD:数据库密码
ini
# =============================================================================
# Windshift 环境变量配置 ------ 纯 HTTP + 服务器 IP 直连版
# =============================================================================
#
# 快速开始:
# 1. cp .env.example .env
# 2. 编辑 .env,至少填 SERVER_IP、SSO_SECRET、POSTGRES_PASSWORD
# 3. docker compose up -d
# 4. 浏览器访问 http://<SERVER_IP>:8080
#
# 本文件与同目录 docker-compose.yml 的 ${变量} 一一对应,改这里即生效。
# 写死在 compose 里的固定值见文末「附录:固定配置」。
#
# 重要提示:
# - 这是明文 HTTP 部署,会话和数据不加密,仅适用于可信内网或试用环境。
# - 修改 POSTGRES_* 后旧数据卷不会迁移,如需重建请先备份再删除卷。
# =============================================================================
# -----------------------------------------------------------------------------
# 1. 访问地址(必填)
# -----------------------------------------------------------------------------
# 服务器 IP 或内网域名。必须是浏览器能直接访问到的地址,
# 它决定登录回调、邮件链接、日历订阅里的域名,填错会导致登录后反复跳转。
SERVER_IP=192.168.0.197
# 对外暴露的端口,默认 8080。改了这里,访问地址同步变成 http://SERVER_IP:新端口
PORT=8256
# -----------------------------------------------------------------------------
# 2. 安全密钥(必填)
# -----------------------------------------------------------------------------
# 会话签名与 SSO 凭据加密密钥。生成方式:
# openssl rand -hex 32
# 生成一次后固定不变。更换会导致所有人被踢下线,且已加密的 SSO/集成凭据失效。
SSO_SECRET=606588438a0363a8ed5611d9ea5fd8de667befbfd44186e7a6ea7a36ae55e104
# -----------------------------------------------------------------------------
# 3. 数据库(PostgreSQL,必填)
# -----------------------------------------------------------------------------
POSTGRES_USER=windshift
# 请改为强密码。密码含 @ : / ? # 等字符也安全,连接串会自动转义。
POSTGRES_PASSWORD=change-me-please
POSTGRES_DB=windshift
# 数据库连接 TLS 模式:disable / allow / prefer / require / verify-ca / verify-full
# 同机容器内网通信保持 disable;指向远程或托管 PostgreSQL 时改 require 或更严格。
POSTGRES_SSLMODE=disable
# 连接池预算(一般无需修改):
# 副本数 × (MAX_READ_CONNS + 5) + POSTGRES_CONNECTION_HEADROOM ≤ PostgreSQL 的 max_connections
# PostgreSQL 默认 max_connections=100,当前配置 1×(30+5)+10=45,安全。
MAX_READ_CONNS=30
POSTGRES_REPLICA_COUNT=1
POSTGRES_CONNECTION_HEADROOM=10
# -----------------------------------------------------------------------------
# 4. 内存预算(可选,两个值需成对调整)
# -----------------------------------------------------------------------------
# 进程内部内存预算,单位 MiB。运行时堆上限和缓存分配由它推导。
WINDSHIFT_MEMORY_LIMIT_MB=2048
# 容器内存硬限额,必须 ≥ 上面的进程预算,否则进程会先被 OOM Killer 杀掉。
WINDSHIFT_CONTAINER_MEMORY_LIMIT=2g
# -----------------------------------------------------------------------------
# 5. 会话与出网策略(可选)
# -----------------------------------------------------------------------------
# 会话 IP 变化时的处理方式:
# log - 记录日志但放行,并把新 IP 更新为绑定点(默认,推荐先用这个观察)
# strict - 直接拒绝,要求重新登录
# off - 完全不比较客户端 IP
SESSION_IP_BINDING=log
# true 允许服务端 HTTP 客户端访问回环/内网地址。
# 需要连内网 Gitea/Jira、宿主机本地 LLM(如 http://172.17.0.1:11434/v1)时保持 true。
# 设为 false 会屏蔽所有私网目标,上述集成全部失效(含 OIDC、SMTP、webhook)。
ALLOW_LOCAL_CONNECTIONS=true
# true 表示对出站 TLS 跳过证书校验(SMTP / IMAP / LDAP / HTTPS 集成)。
# 仅当目标使用自签证书时开启;它会对整个进程关闭服务器身份校验。
TLS_SKIP_VERIFY=false
# -----------------------------------------------------------------------------
# 6. 日志(可选)
# -----------------------------------------------------------------------------
# 级别:debug / info / warn / error
LOG_LEVEL=info
# 格式:json / text / logfmt
LOG_FORMAT=json
# -----------------------------------------------------------------------------
# 7. 附件存储(可选,默认本地磁盘)
# -----------------------------------------------------------------------------
# local 表示存到 /data/attachments(windshift-data 卷内);
# 改为 s3 则使用下面的 S3 兼容对象存储。
STORAGE_BACKEND=local
# 以下仅在 STORAGE_BACKEND=s3 时生效。
# AWS S3:S3_ENDPOINT 留空、S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY 都留空,
# 走 AWS 官方凭据链(环境变量、SSO、实例角色)。
# MinIO / RustFS / Wasabi 等自建:填 S3_ENDPOINT,通常还需 S3_FORCE_PATH_STYLE=true。
S3_ENDPOINT=
S3_BUCKET=
S3_REGION=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_FORCE_PATH_STYLE=false
S3_PREFIX=
# -----------------------------------------------------------------------------
# 8. Web Push 推送(可选)
# -----------------------------------------------------------------------------
# 三项留空即可:首次启动会自动生成 VAPID 密钥对并存入数据库,推送直接可用。
# 仅当你想自行管理或轮换密钥时才填写(密钥对必须长期稳定,轮换会使所有订阅失效)。
# 自行生成:npx web-push generate-vapid-keys
VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
# VAPID 的 sub 联系方式,mailto: 或 https: 开头;留空则使用 BASE_URL。
VAPID_SUBJECT=
# -----------------------------------------------------------------------------
# 9. 监控(可选)
# -----------------------------------------------------------------------------
# 设置后 /metrics 需携带 "Authorization: Bearer <token>";留空则公开,
# 此时应在网络层限制访问。
WINDSHIFT_METRICS_TOKEN=
# =============================================================================
# 附录:固定配置(在 docker-compose.yml 中写死,改本文件无效)
# =============================================================================
#
# BASE_URL=http://<SERVER_IP>:<PORT>
# 由 SERVER_IP 与 PORT 自动拼装。若要换域名或加子路径,改 compose 里的这一行。
#
# ALLOW_INSECURE_HTTP=true
# 允许浏览器从非 localhost 的明文 HTTP 源做带凭据请求。
# 不开启时容器能正常启动,但日志报 "Failed to create CORS middleware ... insecure origin
# patterns",之后浏览器所有请求返回 CORS_CONFIG_ERROR。
#
# USE_PROXY=false
# 无反向代理时必须为 false。为 true 会信任 X-Forwarded-Proto / X-Forwarded-For,
# 可被伪造,导致 IP 限流与安全日志失真。
#
# PORT=8080(容器内)
# 容器内监听端口,固定值;对外端口由上面第 1 节的 PORT 决定。
#
# DB_TYPE=postgres / POSTGRES_HOST=db / POSTGRES_PORT=5432
# 使用 compose 内置的 db 服务,主机名固定为服务名 db。
#
# ATTACHMENT_PATH=/data/attachments / PLUGIN_DIR=/data/plugins
# 位于 windshift-data 卷内,随卷持久化。
#
# CODING_AGENT_ENABLED=true
# 开启 coding-agent 编排面(只排队与分发,实际执行在远程 runner 上)。
#
# --- 如果要换成 HTTPS(推荐正式使用)---
# 1. 在 compose 中把 BASE_URL 改为 https://域名
# 2. 删除 ALLOW_INSECURE_HTTP=true(或显式设 false)
# 3. 在 Windshift 前面加 Caddy/Nginx 终止 TLS,并把 USE_PROXY 改为 true
# 4. 加反代后不要再对外暴露 8080,只允许反代访问
# 5. 如需 passkey 登录,额外设置 WEBAUTHN_RP_ID=域名(裸 IP 不是合法 RP ID)
# =============================================================================
记得保存时要用 UTF-8 编码

docker-compose.yml
将下面的内容保存为 docker-compose.yml 文件:
yaml
# Windshift --- 纯 HTTP + 服务器 IP 部署(内网/测试用)
# 用法:
# 1. 复制 .env.example 为 .env,改 SERVER_IP / SSO_SECRET
# 2. docker compose up -d
# 3. 浏览器访问 http://<SERVER_IP>:8080
services:
windshift:
image: ghcr.io/windshiftapp/windshift:main
container_name: windshift-web
pull_policy: always
restart: unless-stopped
# 直连 IP:8080,必须显式暴露端口(原 compose-main.yml 是给 Dokploy/反代用的,没有 ports)
ports:
- "${PORT:-8080}:8080"
ulimits:
nofile:
soft: 65536
hard: 65536
# 必须:scratch 镜像内没有 /tmp,大附件上传和 coding-agent 都依赖它
tmpfs:
- /tmp:exec,size=64M
environment:
# 关键三条:http + IP
- BASE_URL=http://${SERVER_IP}:${PORT}
- ALLOW_INSECURE_HTTP=true # 允许非 localhost 的明文 HTTP 源(否则 CORS 中间件创建失败)
- USE_PROXY=false # 没有反代就必须是 false,否则会信任伪造的 X-Forwarded-*
- PORT=8080
- SSO_SECRET=${SSO_SECRET}
- SESSION_IP_BINDING=${SESSION_IP_BINDING:-log}
- ATTACHMENT_PATH=/data/attachments
- STORAGE_BACKEND=${STORAGE_BACKEND:-local}
- PLUGIN_DIR=/data/plugins
- DB_TYPE=postgres
- POSTGRES_HOST=db
- POSTGRES_PORT=5432
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
- MAX_READ_CONNS=${MAX_READ_CONNS:-30}
- POSTGRES_REPLICA_COUNT=${POSTGRES_REPLICA_COUNT:-1}
- POSTGRES_CONNECTION_HEADROOM=${POSTGRES_CONNECTION_HEADROOM:-10}
- ALLOW_LOCAL_CONNECTIONS=${ALLOW_LOCAL_CONNECTIONS:-true}
- CODING_AGENT_ENABLED=true
- LOG_LEVEL=info
- LOG_FORMAT=json
- WINDSHIFT_MEMORY_LIMIT_MB=${WINDSHIFT_MEMORY_LIMIT_MB:-2048}
volumes:
- ./data:/data
mem_limit: ${WINDSHIFT_CONTAINER_MEMORY_LIMIT:-2g}
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "/windshift", "healthcheck"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
db:
image: postgres:17
container_name: windshift-db
restart: unless-stopped
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- ./pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5
PostgreSQL默认max_connections是100。Windshift的连接预算是「副本数 × (MAX_READ_CONNS+5) + 预留」,启动时会检查这个预算是否安全,超了会在日志里告警。
执行以下命令启动:
bash
# 进入 windshift 目录
cd /volume1/docker/windshift
# 将 docker-compose.yml 和 env.txt 放入当前目录
# 一键启动
docker-compose --env-file env.txt up -d

运行
在浏览器中访问 http://<群晖IP>:8256 即可进入语言设置

接下来是管理员设置

模块默认即可

刚开始使用,还需要创建工作区

输入名称名称和键值(Key)
创建第一块工作区时有个坑:Key 不是名称 。它是工作项编号的前缀(如
DEV-123),规则是从源码里写死的------只允许A-Z a-z 0-9,长度 2 到 10 位,不能有空格、中文、连字符。名称可以随便用中文,但Key请单独填一段英文缩写,比如DEV、OPS、CS。Key全局唯一,且建好后改动会让所有历史工作项编号前缀一起变,建议一次定好。

接下来还需要创建工作项

例如:编写 Windshift 安装教程

还可以设置更详细的子项、附件、说明等

更具体的使用请参考官方的说明文档
注意事项
/tmp的tmpfs不能省 :镜像是scratch构建,里面没有/tmp目录。缺少这个挂载,大附件上传会在超出32 MiB内存阈值后写盘失败。必须用短语法- /tmp:exec,size=64M,Docker默认的noexec会让git无法执行askpass辅助程序。- 明文 HTTP 只支持 localhost :
BASE_URL填http://192.168.0.100:8080时,容器能正常启动,但日志会报Failed to create CORS middleware ... insecure origin patterns,之后浏览器所有请求返回CORS_CONFIG_ERROR。两种解法:加反向代理做HTTPS(推荐),或在内网可信环境下显式开启ALLOW_INSECURE_HTTP=true。 - Passkey 与 PWA 需要 HTTPS :明文
HTTP下账号密码登录正常,但Passkey注册、PWA安装、Web Push推送都无法使用,因为浏览器要求安全上下文,且裸IP不是合法的RP ID。 USE_PROXY只在有反代时为 true :为true会信任X-Forwarded-Proto和X-Forwarded-For,直接暴露端口时可被伪造,导致IP限流与安全日志失真。用Traefik/Nginx时改为true,并停止对外暴露8080。- 备份
/data整个目录 :SQLite模式下数据库、附件、插件、生成的VAPID密钥都在同一个卷里。用PostgreSQL也别漏了附件目录,数据库备份不能覆盖它。 - 内存给足 2G :看板、树视图、
SSE长连接都比较吃内存。1G能跑起来,但几十人同时在线会开始换页。WINDSHIFT_MEMORY_LIMIT_MB与容器的mem_limit要成对调整。 pull_policy: always的意义 :如果跟随main这类可变tag,Dokploy等平台执行compose up -d前不会自动pull,可能一直跑旧镜像。固定版本号(如v0.9.1)最稳。- 中文界面已内置:九种语言里包含简体中文,登录后在个人设置里切换,不需要额外装语言包。
参考文档
Windshiftapp/core: Self-hosted, open-source work management platform and Jira alternative (AGPL v3)
地址:https://github.com/Windshiftapp/core
Windshift 官方网站地址:https://windshift.sh
Docker 部署文档(含反向代理、PostgreSQL、WebAuthn 说明)地址:https://windshift.sh/self-hosting/02-deployment/01-docker
配置项完整说明(环境变量与命令行参数对照)地址:https://windshift.sh/self-hosting/03-configuration/01-options