02存储后端
默认走 文件系统,开箱即用。需要弹性扩容时切到 对象存储 —— 同一份二进制,热重载不重启。
filesystem(默认)
所有 blob/manifest 落到 DATA_DIR 下的子目录。无需额外配置,适合单机部署或测试环境。
S3 / R2 / MinIO / B2 / Spaces
管理后台 → System Settings → Storage,切到 s3 并填如下字段:
- region
- S3 区域,如
us-east-1。MinIO/R2 等填 auto 或留空。
- bucket
- 桶名。
- access_key / secret_key
- credentials;
secret_key 在 PATCH 时空字符串 = 保留原值。
- region_endpoint
- 非 AWS S3 必填,如
https://<account>.r2.cloudflarestorage.com。
- force_path_style
- MinIO / 旧版 S3 需要
true;R2 / AWS 留 false。
- root_directory
- 桶内前缀。多环境共享一桶时建议设置,例如
prod/。
本地 L1 缓存(仅 S3 模式)
把热 layer 缓存在本机磁盘,命中后跳过远端往返。Storage 页底部 Local Cache 卡片可调上限与 LRU 天数。0 = 关闭,纯走 S3。
test before save
Storage 页提供 Probe 按钮 —— 不写库、不切 manager,按"假如保存"的配置跑一次 put/stat/get/delete,凭据 / endpoint 写错时第一时间反馈。
03镜像加速
stashhub 可同时作为 registry 与 pull-through cache。配一个独立域名给镜像加速使用,docker daemon 把它当 mirror,第一次拉穿透回源,之后局域网命中。
1) 配镜像加速域名
管理后台 → Site Settings → mirror_domain,填一个 不同于 registry_hostname 的裸域名,例如 mirror.stash-hub.one。两个域名都解析到同一台 stashhub。
为什么要两个域名
镜像加速是反向代理 Docker Hub / GHCR 的内容到自己域下;如果跟你的私有 registry 共用一个 host,会出现命名冲突(攻击者可以推送同名仓库覆盖上游内容)。结构性隔离 = 装不出来这种攻击。
2) 加上游
Admin → Mirror → Upstreams。一个上游 = 一个 path prefix + remote URL + 可选凭据。客户端访问 /v2/[prefix]/[image] 时按 prefix 路由到对应上游。
- alias
- 展示名,比如
dockerhub。
- path_prefix
- URL 前缀,匹配
^[a-z0-9][a-z0-9_-]{0,62}$。
- remote_url
- 上游 v2 端点,如
https://registry-1.docker.io。
- username / password
- 可选;
AES-256-GCM 加密落盘,密钥派生自 JWT_SECRET。
- is_default_mirror
- 客户端不写 prefix 时(如
docker pull alpine)落到哪个上游。最多 1 个。
3) 客户端 daemon 配置
每台需要加速的机器上:
curl -fsSL https://stash-hub.one/setup-mirror.sh | sudo bash
# 脚本会自动检测 Docker Desktop / Linux + systemd / Colima / OrbStack,
# 备份原 daemon.json 后把 mirror endpoint 合并进 registry-mirrors。
缓存策略
Admin → Mirror → Policy。四维淘汰:
lru_days — 未访问超过 N 天淘汰
max_cache_bytes — 总容量上限
min_hits_to_cache — 首次拉不缓存,到 N 命中才落盘
tag_whitelist_regex — 只缓存符合正则的 tag(屏蔽 nightly / dev 等噪音)
Manifest TTL(manifest_ttl_hours)参考 Artifactory:到期后 HEAD 上游比 digest,未变只刷时间戳,几乎零开销。
04运行期配置
几乎所有可调项都在 Web 管理面板。改一个字段,立即生效;不存在"改 yaml 然后重启"的环节。
Site Settings
- site_name
- 显示在导航和邮件里的品牌名。
- registry_hostname
- 私有 registry 域名(push/pull 用)。
- mirror_domain
- 镜像加速专用域名,必须 ≠
registry_hostname。留空 = 不开启镜像加速。
- public_signup_enabled
- 是否允许匿名注册账号。
- default_visibility
- 新建仓库的默认可见性,
public / private。
SMTP
配置完 SMTP 后,Send Test Email 按钮会向当前 admin 的注册邮箱发一封测试邮件 —— 收件人锁死自己,防止账号被攻陷后变成 SMTP 跳板。
Retention(保留策略)
全局默认 + 仓库级 override。GC 提供 dry-run,先看会删掉哪些 tag 再决定要不要执行。
Trusted Proxies / Real IP
站在反代后面时配置 CIDR 列表与 header 名,让审计日志记录真实客户端 IP 而不是反代。空列表 = 关闭。
05管理 API
所有 Web UI 操作都走 /api/v1,可以直接对接 CI / 自动化脚本。鉴权用浏览器 session cookie 或 API token。
- GET /api/v1/config
- 公开 —— 站点名、注册开关、镜像加速 endpoint。
- GET /api/v1/admin/settings
- 完整运行期配置(secret 字段以 *_set 布尔位返回)。
- PATCH /api/v1/admin/settings
- 局部更新;
nil 字段不变。Storage 改动触发热重载,失败自动回滚。
- POST /api/v1/admin/storage/test
- 用提交的 patch 跑一次 put/stat/get/delete,不写库、不切 manager。
- GET /api/v1/admin/upstreams
- 列镜像加速上游。
- POST / PATCH / DELETE
- CRUD 后自动 reload proxy 集合,失败回滚或保留状态视上下文而定。
- POST /api/v1/admin/mirror/sweep
- 手动触发一次缓存清理 sweeper。
- GET /api/v1/admin/mirror/stats
- 按上游聚合的实时命中率与容量占用。
完整 OpenAPI schema 见仓库 internal/webapi/ 目录。
06常见问题
忘了 admin 密码?
在服务端跑 ./stashhub admin reset-password <email>,会生成一次性令牌走标准重置流程,避免直接改 hash 留死角。
docker login 时密码栏应该填什么?
推荐填 PAT(Personal Access Token),不要直接用登录密码。Docker 26+ 默认走 containerd image store,containerd 的凭据路径与 moby legacy 不一致,PAT 是后端校验的明文 token,pull 走 containerd resolver 时认证更稳。PAT 前缀为 sthb_。
怎么生成 PAT?
Web UI 右上角头像 → 设置 → Tokens,点 新建。可选填过期时间(不填 = 永不过期)。注意:token 只在创建那一次完整显示,离开页面后只能看到前几位,请立即拷到密码管理器。当前实现下 PAT 不分 scope —— 一个 token 等同于该账号的全部权限,按主密码强度托管。
公有仓库需要 login 才能 pull 吗?
不需要。visibility = public 的仓库允许匿名 pull —— docker pull host/repo:tag 直接生效,不必先 docker login。但 push、delete、查看协作者列表始终强制鉴权,匿名访问只对 pull 开口子。
怎么把仓库共享给别人?
两种粒度:
- 改可见性 —— 仓库设置 → Visibility 切到
public,任何人匿名 pull。
- 加协作者 —— 仓库设置 → Members,输入用户名并选角色:
reader(只 pull)/ writer(push + pull)/ admin(含改设置、删仓库)。owner 默认隐含最高权限,不必把自己加进 members。
怎么删一个 tag 或整个仓库?
Web UI:仓库页 → tag 行操作菜单可单独删 tag;仓库设置最底"危险区"可整库删除。CI / 脚本:DELETE /api/v1/repositories/{owner}/{name}/tags/{tag} 或 DELETE /api/v1/repositories/{owner}/{name}。注意:元数据立即消失,但底层 blob 占用要等垃圾回收才释放 —— 手动触发可在 Admin → Mirror → Sweep,或等定时任务跑。
registry 必须 HTTPS 吗?
是。Docker daemon 默认按 HTTPS 跟 registry 握手,裸 HTTP 部署在 docker login / GitLab CI docker push 时会撞到这个错(GitLab Runner 日志里也是同一行):
Error response from daemon: Get "https://your.host/v2/":
http: server gave HTTP response to HTTPS client
两种修法,挑一个:
方案 A · HTTPS 反代(推荐,长期)。在 stashhub 前面挂 Caddy / nginx / Traefik 做 TLS 终端,EXTERNAL_URL 改成 https://...。Caddy 一行配置就能拿 Let's Encrypt 自动签发:
your.host {
reverse_proxy stashhub:8080
}
内网拿不到公网证书可以用 mkcert / cfssl 自签 CA,把 CA 发到所有 Runner / 客户机,装到 /etc/docker/certs.d/<域名>/ca.crt。push 凭据从此走线上加密,生产请走这条。
方案 B · insecure-registries(应急,仅内网)。在每台跑 docker login 的机器(包括 GitLab Runner)改 /etc/docker/daemon.json:
{
"insecure-registries": ["your-stashhub-host:port"]
}
然后 systemctl restart docker。host 字段不带 http:// / https:// 前缀。如果 Runner 跑 docker-in-docker,flag 必须传给 dind service,而不是宿主机:
# .gitlab-ci.yml
services:
- name: docker:dind
command: ["--insecure-registry=your-stashhub-host:port"]
这条路径下 PAT / 登录密码走明文,内网嗅探就能拿到 —— 只在测试 / 隔离环境用,长期还是补 TLS。
反代后 docker push 大镜像报 413?
nginx 默认 client_max_body_size 1m,blob 上传几乎一定超。server / location 块里改成 client_max_body_size 0;(不限)或至少 4G。Caddy 默认不限,没这个坑;Traefik 看 buffering.maxRequestBodyBytes。
从 filesystem 切到 S3,旧镜像怎么办?
旧数据 不会自动迁移。Storage 切换是热重载新 manager,旧目录里的 blob/manifest 仍在磁盘但 registry 不会再读,等于变孤儿。要保留先迁后切:
aws s3 sync <DATA_DIR>/registry/ s3://<bucket>/<root_directory>/(保留同名前缀);
- 对照新桶能列到对应路径后,再在管理后台切 storage。
反向(S3 → filesystem)同理。Storage 页的 Probe 只测连通性,不验证内容是否搬过去了。
mirror 缓存策略默认值合理吗?
默认对大多数团队够用:lru_days = 30、max_cache_bytes = 50 GiB、min_hits_to_cache = 1(首次拉就缓存)、manifest_ttl_hours = 2。需要调的场景:
- 磁盘紧 → 调小
max_cache_bytes 与 lru_days;
- 上游 tag 频繁动(
:latest / :edge)→ 缩短 manifest_ttl_hours 到 1 或更短;
- 不想被
nightly / dev 噪音塞满 → tag_whitelist_regex 只放行你关心的;
- 只想缓存"真热"的镜像 →
min_hits_to_cache 提到 2~3,首次拉穿透不落盘。
为什么 docker pull 仍然走外网?
检查三件事:
(1) docker info | grep -A1 'Registry Mirrors' 是否包含你的 mirror endpoint;
(2) mirror_domain DNS 是否解析到 stashhub;
(3) EXTERNAL_URL 的 scheme 与边缘 TLS 终端一致 —— 错配会导致 docker daemon 静默回退到 docker.io。