Docs/ 基础设施/ 卷与挂载

卷与挂载

将 Docker 卷、主机目录或单个文件挂载到应用容器中,使数据在重启和部署之间得以保留。

为什么需要挂载

每次部署都会启动一个新容器。应用写入自身文件系统的内容会随上一个容器一起消失:上传的文件、SQLite 数据库、生成的缓存。挂载会附加一份位于容器之外的存储,下一个容器就能在同一路径找到同样的数据。

Note
挂载在下一次部署时生效。添加或删除挂载后,请重新部署应用(sh0 deploy my-api --wait)。由 sh0 创建的数据库服务器自行管理存储,无需在此为其添加挂载。

挂载类型

  • Volume -- 由 sh0 创建、名为 sh0-<app>-<name> 的 Docker 卷。适合存放应用数据。
  • Bind Mount -- 将主机目录挂载到容器中。只接受位于允许根目录之下的目录(见下文)。
  • File Mount -- 单个文件,内容在控制面板中输入,适用于配置文件。sh0 保存内容并在部署时将文件写到主机上。

添加挂载

  1. 打开应用,进入 Volumes 选项卡。
  2. 点击 Add Mount 并选择类型。
  3. 填写 Mount Name(对卷而言,它会成为 Docker 卷名的一部分)和 Container Path,即 /data 这样的绝对路径。
  4. 绑定挂载需填写 Host Path;文件挂载需填写 File Content。如应用不应写入,请勾选 Read-only。
  5. 点击 Add Mount 确认,然后重新部署应用。

通过 API 完成同样操作(真实响应;source 为必填项,新卷时留空):

Terminal
$ curl -X POST https://panel.example.com/api/v1/apps/<app-id>/mounts \
    -H "Authorization: Bearer $SH0_TOKEN" -H "Content-Type: application/json" \
    -d '{"mount_type":"volume","name":"data","source":"","target":"/data"}'
{"data":{"app_id":"<app-id>","created_at":"2026-10-02 02:49:02","id":"0aa5cb7d-d97a-4cce-90c3-c61c338f0055","mount_type":"volume","name":"data","read_only":false,"source":"sh0-my-api-data","target":"/data"}}

mount_type 取值为 volume、bind 或 file。source 对新卷留空,对绑定挂载为主机路径,对文件挂载为文件内容。read_only 默认为 false。创建挂载需要在应用所属项目上拥有 developer 角色。

数据存放位置

  • Volume -- 位于 Docker 自己的目录中,默认安装下为 /var/lib/docker/volumes/sh0-<app>-<name>/_data。
  • File Mount -- /var/lib/sh0/file-mounts/<app-id>/<mount-id>/<文件名>,以绑定方式挂载到容器路径。
  • Bind Mount -- 指向您指定的位置,前提是路径位于允许的根目录之下:以 root 运行的安装默认为 /var/lib/sh0/mounts。sh0 服务的环境变量 SH0_BIND_MOUNT_ROOTS 可替换该列表(以冒号分隔的路径);设为空值则拒绝所有绑定挂载。
Terminal
$ docker volume inspect sh0-my-api-data --format '{{.Mountpoint}} {{json .Labels}}'
/var/lib/docker/volumes/sh0-my-api-data/_data {"sh0.app_name":"my-api","sh0.managed":"true","sh0.mount_path":"/data"}

不在允许根目录下的主机路径会在创建挂载时被拒绝(真实响应):

Response
{"error":{"code":"VALIDATION_ERROR","message":"Bind mount source '/etc' is outside the allowed roots (/var/lib/sh0/mounts). Set SH0_BIND_MOUNT_ROOTS to authorise another root."}}

sh0 不会更改挂载数据的属主或权限。容器以镜像声明的用户写入:以 root 运行的镜像以 root 写入;声明了非 root USER 的镜像需要一个该用户可写的目录。

从其他平台导入数据

要从其他主机迁移应用的文件,请先在原主机上打包,再用 sh0 volumes <app> import 将归档解压到 sh0 卷中。归档(tar,未压缩或经 gzip、bzip2、xz 压缩,最大 500 MB)通过 Docker 直接写入卷本身,无论应用正在运行、已停止还是尚未部署。常见的迁移顺序:sh0 apps create、sh0 volumes <app> create /data、导入,然后执行第一次 sh0 deploy。

Terminal
$ sh0 apps create my-site --image nginx:1.27-alpine --port 80
[ok] Created 'my-site' (25fc66d6-82b4-4aea-998b-d3cb0844162e), not deployed: set its variables, then `sh0 deploy my-site`
$ sh0 volumes my-site create /usr/share/nginx/html
[ok] Volume sh0-my-site-25fc66d6-usr-share-nginx-html created, mounted on /usr/share/nginx/html at the next deploy
$ sh0 volumes my-site import /usr/share/nginx/html site.tar.gz
[ok] Imported site.tar.gz (215 bytes) into sh0-my-site-25fc66d6-usr-share-nginx-html at /
$ sh0 deploy my-site --wait
Warning
目录挂载(bind mount)陷阱。在源平台上打包之前,先确认容器实际挂载的是什么。许多面板(包括 Easypanel)挂载的是主机目录(Type: bind),而不是 Docker 卷:文件存放在该目录中,而名称相近的 Docker 卷——其 /var/lib/docker/volumes/<name>/_data——可能是空的,容器停止、无人写入时尤其明显。此时打包 _data 只会迁移一个空目录,且不报任何错误。在 sh0 一侧同理:切勿手动把文件复制到 _data,请使用导入。

从容器本身读取每个挂载的来源,并从该路径打包:

Terminal
$ docker inspect <container> --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{println}}{{end}}'
$ tar -czf data.tar.gz -C <Source of the mount, as printed above> .

删除挂载

在 Volumes 选项卡中删除挂载后,下一次部署将不再包含它。对于卷,sh0 还会尝试删除 Docker 卷,但只要仍有容器在使用,Docker 就会拒绝——应用运行期间正是如此。此时数据仍留在磁盘上,该卷会以 Docker 标记重新出现在列表中,且没有删除按钮。要真正删除数据:

  1. 在 Volumes 选项卡中删除挂载。
  2. 重新部署应用,使不再有容器使用该卷。
  3. 通过 API 删除该卷:
Terminal
$ curl -X DELETE https://panel.example.com/api/v1/apps/<app-id>/volumes/sh0-my-api-data \
    -H "Authorization: Bearer $SH0_TOKEN"
Warning
删除应用不会删除其卷,无论是在控制面板中还是使用 sh0 delete。只有 API 调用 DELETE /api/v1/apps/<id>?delete_volumes=true 会删除它们。否则,之后同名且挂载名相同的应用会重新找到旧数据。

限制

  • 没有大小限制,也不能调整大小。卷会一直增长到主机磁盘写满。请关注服务器磁盘。
  • 每个容器路径只能有一个挂载。同一路径上的第二个挂载会被拒绝:“A mount already targets '/data'. Delete it first, or choose another target.”
  • 没有自动快照。挂载不会自行备份;备份需在 Backups 页面单独配置。
  • 仅限本地。卷位于运行应用的服务器上;挂载没有网络存储或对象存储后端。