Documentation · v1

从 docker compose up 到
第一次 push。

本文档覆盖 stashhub 的安装、存储后端、镜像加速、运行期配置与常见问题。所有可调项都在管理面板,本页给出概念地图与精确字段。

01快速开始

stashhub 是单二进制 + SQLite 默认就能跑。docker compose up 起来后,浏览器打开 http://localhost:8080,按引导设置第一个 admin 即可。

Docker Compose

docker-compose.yml
services:
  stashhub:
    image: stashhub/stashhub:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./data:/var/lib/stashhub
    environment:
      # 必填:外部访问地址(决定 mirror endpoint 的 scheme)
      EXTERNAL_URL: https://stash-hub.one
      # 必填:JWT secret(≥ 32 字节随机串)
      JWT_SECRET: "$(openssl rand -hex 32)"

第一次启动

  • 访问 /,Web UI 会引导创建第一个 admin 账号。
  • 登录后进入 /admin,所有运行期配置都在这里 —— 改完即时生效,无需重启。
  • 右上角创建第一个仓库,docker login + docker push 验证链路通畅。
tip · external_url

EXTERNAL_URL 影响生成的镜像 endpoint、setup-mirror.sh 脚本里的 URL,以及 CORS 行为。一定要写最终用户访问的协议 + 域名,比如 https://stash-hub.one。

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 配置

每台需要加速的机器上:

bash · 一键脚本
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。