Speedle 发布一个镜像,其中包含 speedle-pms、speedle-ads 和 spctl。镜像基于 scratch 构建,以 UID 65532 运行,里面没有 shell。第一个参数决定启动哪个程序。
两个仓库上的镜像索引相同:
- Docker Hub:
docker.io/speedle/speedle - GHCR:
ghcr.io/teramoby/speedle
1.7.0 在两个仓库都发布了标签 1.7.0、1.7 和 latest,架构为 linux/amd64 和 linux/arm64。
拉取镜像
docker pull docker.io/speedle/speedle:1.7.0
docker pull ghcr.io/teramoby/speedle:1.7.0docker pull 会打印索引摘要。1.7.0 在两个仓库上的摘要都是 sha256:20bd3fe8169ec3ddadf1484285e34bd68004dbb457782223dc84a2a9f1dd91c0。
选择标签
1.7.0 指向这一次发布。更高的 1.7 补丁发布后,1.7 会跟着移动。从 master 最新提交发布更高版本时,latest 会跟着移动。同一次版本可以再次发布,因此标签不是不可变的构建。
查看索引摘要:
docker buildx imagetools inspect docker.io/speedle/speedle:1.7.0 --format '{{.Manifest.Digest}}'1.7.0 这次发布里,两个仓库上的 1.7.0、1.7 和 latest 都打印:
sha256:20bd3fe8169ec3ddadf1484285e34bd68004dbb457782223dc84a2a9f1dd91c0部署 1.7 或 latest 之前请再查一次摘要。要固定在同一次构建上,把镜像名写成带 @ 和该摘要的形式,例如 docker.io/speedle/speedle@sha256:20bd3fe8169ec3ddadf1484285e34bd68004dbb457782223dc84a2a9f1dd91c0。
镜像里的命令
不带参数时,镜像打印这三个程序:
docker run --rm docker.io/speedle/speedle:1.7.0speedle-pms
speedle-ads
spctldocker run --rm ghcr.io/teramoby/speedle:1.7.0 打印同样的三行。下面的命令使用 Docker Hub 的名字。把名字换成 GHCR 即使用那一份副本。镜像里没有 shell,所以 docker exec 必须用绝对路径调用 /spctl、/speedle-pms 或 /speedle-ads。
运行 PMS 和 ADS
创建命名卷并启动策略管理服务。进程在容器内监听 0.0.0.0:6733。把端口发布到 127.0.0.1:6733,未认证的管理 API 就只留在本机。
docker volume create speedle-policies
docker run -d --name speedle-pms \
-p 127.0.0.1:6733:6733 \
-v speedle-policies:/data \
docker.io/speedle/speedle:1.7.0 \
speedle-pms --store-type file --filestore-loc /data/policies.json --endpoint 0.0.0.0:6733 --insecure=true新建的命名卷会放入镜像里的 /data 目录,UID 65532 可以写入。策略文件不存在时,PMS 会创建 /data/policies.json。镜像不包含 CA 证书。--insecure=true 只用于这个本地 HTTP 示例。TLS 见安全。
Docker 可能在进程开始监听之前就接受已发布的端口,然后重置连接。请重试所有错误,包括这种重置:
curl --fail --silent --show-error --retry 30 --retry-all-errors --retry-delay 1 --connect-timeout 2 \
http://127.0.0.1:6733/policy-mgmt/v1/service第一次尝试可能会打印 curl: (56) Recv failure: Connection reset by peer。随后的尝试返回正文。空存储的响应是:
[]在同一个卷上启动 ADS。它读取 PMS 写入的文件,也包括之后的更新。
docker run -d --name speedle-ads \
-p 127.0.0.1:6734:6734 \
-v speedle-policies:/data \
docker.io/speedle/speedle:1.7.0 \
speedle-ads --store-type file --filestore-loc /data/policies.json --endpoint 0.0.0.0:6734 --insecure=true检查健康和就绪
PMS 不提供 /healthz。上面的 GET /policy-mgmt/v1/service 返回成功,就表示管理 API 已经在接受请求。
ADS 提供存活和就绪检查:
curl --fail --silent --show-error --retry 30 --retry-all-errors --retry-delay 1 --connect-timeout 2 \
http://127.0.0.1:6734/healthz
curl --fail --silent --show-error --retry 30 --retry-all-errors --retry-delay 1 --connect-timeout 2 \
http://127.0.0.1:6734/readyz{"status":"ok"}
{"ready":true,"servicesTotal":0,"servicesUsable":0}进程运行期间,/healthz 返回 200 {"status":"ok"}。ADS 当前能够做授权判断时,/readyz 返回 200。文件存储没有策略分发状态,所以服务计数保持为零;要看的字段是 ready。镜像里没有 shell,也没有 curl,因此这些探测在宿主机或编排器上对已发布的端口执行。http://localhost:6734/healthz 访问的是同一个已发布端口。
用 spctl 创建策略
在正在运行的 PMS 容器里,/spctl 通过该容器自己的端口访问 PMS:
docker exec speedle-pms /spctl --pms-endpoint http://127.0.0.1:6733/policy-mgmt/v1/ create service docs
docker exec speedle-pms /spctl --pms-endpoint http://127.0.0.1:6733/policy-mgmt/v1/ create policy -c "grant user alice get,list /docs/public" --service-name=docsservice created
{"name":"docs","type":"application","metadata":{"createtime":"2026-11-10T03:30:03Z"}}
policy created
{"id":"zhtbsgm54iiuwky4zgmq","name":"","effect":"grant","permissions":[{"resource":"/docs/public","actions":["get","list"]}],"principals":[["user:alice"]],"metadata":{"createtime":"2026-11-10T03:30:04Z"}}PMS 会分配 createtime 和策略 id,所以每次运行这两个值都不同。这里的服务和策略与 SDK 概览 中的示例相同。
同一个 spctl 也可以在另一个容器里运行,只要它与 PMS 共用网络命名空间:
docker run --rm --network container:speedle-pms \
docker.io/speedle/speedle:1.7.0 \
spctl --pms-endpoint http://127.0.0.1:6733/policy-mgmt/v1/ get service --all向 ADS 请求决策
curl --fail --silent --show-error --header 'Content-Type: application/json' \
--data '{"subject":{"principals":[{"type":"user","name":"alice"}]},"serviceName":"docs","resource":"/docs/public","action":"get"}' \
http://127.0.0.1:6734/authz-check/v1/is-allowed{"allowed":true,"reason":0}reason 为 0 表示 GRANT_POLICY_FOUND。同一条策略没有授予 delete:
curl --fail --silent --show-error --header 'Content-Type: application/json' \
--data '{"subject":{"principals":[{"type":"user","name":"alice"}]},"serviceName":"docs","resource":"/docs/public","action":"delete"}' \
http://127.0.0.1:6734/authz-check/v1/is-allowed{"allowed":false,"reason":3}reason 为 3 表示 NO_APPLICABLE_POLICIES。ADS 已经在运行时,通过 PMS 写入的新策略会在下一次请求中生效。请求字段见授权决策。
让 SDK 连接 ADS
SDK 页面使用 http://localhost:6734。按上面的方式发布端口后,这个地址就是本页的 ADS。下面的 Node.js 客户端使用容器实际发布的 http://127.0.0.1:6734。先创建示例目录,再把客户端存成该目录里的 main.mjs:
mkdir speedle-example
cd speedle-exampleimport { Auth, SpeedleClient } from "@speedle/sdk";
const speedle = new SpeedleClient({
backend: "speedle",
endpoint: "http://127.0.0.1:6734",
auth: Auth.none(),
serviceName: "docs",
});
try {
const allowed = await speedle.isAllowed("alice", "get", "/docs/public");
console.log(`alice get /docs/public: allowed=${allowed}`);
} finally {
await speedle.close();
}
在 speedle-example 里安装已发布的包并运行 main.mjs。安装命令与 Node.js SDK 页面相同,并固定同一版本:
npm init -y
npm pkg set type=module
npm install @speedle/sdk@0.2.1
node main.mjsalice get /docs/public: allowed=true其他语言页面使用同一个端点和服务名。SDK 只对回环地址接受明文 http://。
用 Compose 同时运行两个服务
把下面的内容存成 compose.yaml。这是运行同样两个进程和同一个卷的另一种方式。如果前面的容器还在运行,先停掉它们,让 6733 和 6734 空出来。删掉那个卷也会删掉里面的策略文件。
services:
pms:
image: docker.io/speedle/speedle:1.7.0
command:
- speedle-pms
- --store-type
- file
- --filestore-loc
- /data/policies.json
- --endpoint
- 0.0.0.0:6733
- --insecure=true
ports:
- 127.0.0.1:6733:6733
volumes:
- speedle-policies:/data
ads:
image: docker.io/speedle/speedle:1.7.0
command:
- speedle-ads
- --store-type
- file
- --filestore-loc
- /data/policies.json
- --endpoint
- 0.0.0.0:6734
- --insecure=true
ports:
- 127.0.0.1:6734:6734
volumes:
- speedle-policies:/data
volumes:
speedle-policies:docker compose -f compose.yaml up -dup -d 会在 PMS 开始监听之前返回。创建服务和策略之前,先等待 PMS 的服务列表以及 ADS 的 /healthz 和 /readyz。第一次访问 PMS 时连接可能被重置,原因见上文。
curl --fail --silent --show-error --retry 30 --retry-all-errors --retry-delay 1 --connect-timeout 2 \
http://127.0.0.1:6733/policy-mgmt/v1/service
curl --fail --silent --show-error --retry 30 --retry-all-errors --retry-delay 1 --connect-timeout 2 \
http://127.0.0.1:6734/healthz
curl --fail --silent --show-error --retry 30 --retry-all-errors --retry-delay 1 --connect-timeout 2 \
http://127.0.0.1:6734/readyz
docker compose -f compose.yaml exec -T pms /spctl --pms-endpoint http://127.0.0.1:6733/policy-mgmt/v1/ create service docs
docker compose -f compose.yaml exec -T pms /spctl --pms-endpoint http://127.0.0.1:6733/policy-mgmt/v1/ create policy -c "grant user alice get,list /docs/public" --service-name=docs再发送同一个 is-allowed 请求。ADS 不需要重启就能看到新策略。
docker compose -f compose.yaml down -vdown -v 会删除 Compose 卷以及保存在里面的策略。
核对来源证明
发布构建附带了来源证明和 SBOM(docker buildx build --provenance=mode=max --sbom=true)。查看索引:
docker buildx imagetools inspect docker.io/speedle/speedle:1.7.0索引列出 linux/amd64、linux/arm64,以及两份 unknown/unknown 清单。这两份未知平台是证明清单。每一份都把 vnd.docker.reference.type 设为 attestation-manifest,并把 vnd.docker.reference.digest 设为它所描述的平台镜像。docker buildx imagetools inspect ghcr.io/teramoby/speedle:1.7.0 打印相同的平台摘要和证明摘要。
停止容器
docker rm -f speedle-pms speedle-ads
docker volume rm speedle-policiesdocker volume rm 会删除策略文件。如果进程是用 Compose 启动的,改用 docker compose -f compose.yaml down -v。