> ## Documentation Index
> Fetch the complete documentation index at: https://ppio.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 挂载对象存储 Bucket

export const SandboxConfigHint = () => {
  if (typeof document === "undefined") {
    return null;
  } else {
    return <Note>在运行本文档中的示例代码前，请确保您已正确配置环境变量，详情请参考 <a href="/sandbox/get-start#配置环境变量">配置环境变量</a>。</Note>;
  }
};

每个沙箱实例默认分配 20GB 的系统盘存储空间，用于临时存储数据。<u>沙箱关闭后，该存储空间中的所有数据将被自动清理</u>。因此，建议您将需要长期保存的数据存储到独立的云存储服务中。

<Tip>
  沙箱实例分配的存储空间大小后续可能会调整，具体以 [计费说明](/sandbox/pricing) 文档说明为准。
</Tip>

对象存储（Object Storage）是一种稳定、高性价比、高性能的云存储服务，主流云服务商均提供此类产品。您可以在沙箱中使用云服务商提供的 SDK 或 CLI 工具直接操作对象存储，也可以使用 FUSE 文件系统工具将对象存储 Bucket 挂载到沙箱的指定目录，从而像操作本地文件系统一样使用对象存储。

<Tip>
  [FUSE（Filesystem in Userspace）](https://www.kernel.org/doc/html/next/filesystems/fuse.html)是一个用户空间文件系统框架，它可用于将各种云存储服务 “伪装成” 本地普通文件夹，只需要像操作普通文件夹一样使用它们。
</Tip>

本文档将介绍如何将主流云服务商提供的对象存储 Bucket 挂载到沙箱中，以便后续使用。

<Warning>
  通过以下方式挂载对象存储 Bucket 到本地目录，通常文件读写性能较差，如果您的业务对文件读写性能敏感，不建议使用这种方式。同时通过这种方式操作对象存储并不是原子性的，也就是存在本地操作成功但对象存储操作失败的风险，进而导致数据丢失。

  因此这种方式比较适用于对读写性能不敏感，且没有频繁写操作的场景。否则建议您优先使用云服务商提供的 SDK 或 CLI 工具在业务逻辑中直接操作对象存储。
</Warning>

<SandboxConfigHint />

## Amazon S3 对象存储

可以通过 [s3fs](https://github.com/s3fs-fuse/s3fs-fuse) 工具将 Amazon S3 对象存储 Bucket 挂载到沙箱中。

您可以在制作 [沙箱模板](/sandbox/sandbox-template) 时在 `ppio.Dockerfile` 文件中添加安装 s3fs 的命令，或者在沙箱实例内执行安装命令来临时安装。

以下是一个 `ppio.Dockerfile` 示例，展示了如何在制作沙箱模板时安装 s3fs 工具。

<CodeGroup>
  ```dockerfile ppio.Dockerfile icon="docker" theme={null}
  # You can use most Debian-based base images
  FROM ubuntu:latest

  # 注意，安装的 s3fs 版本较低，可能会导致挂载失败，请保证安装的 s3fs 版本高于 1.93
  RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y s3fs
  ```
</CodeGroup>

下面展示了如何在沙箱实例中通过 s3fs 来挂载 Amazon S3 Bucket。

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={null}
  import { Sandbox } from 'ppio-sandbox'

  const TEMPLATE_ID = process.env.PPIO_TEMPLATE_ID
  const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID
  const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY
  const AWS_BUCKET_NAME = process.env.AWS_BUCKET_NAME
  if (!TEMPLATE_ID || !AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY || !AWS_BUCKET_NAME) {
      throw new Error('environment variable PPIO_TEMPLATE_ID or AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY or AWS_BUCKET_NAME is not set')
  }

  const MOUNT_DIRECTORY = "/mnt/s3-bucket"

  const sandbox = await Sandbox.create(TEMPLATE_ID)

  // 创建挂载目录
  await sandbox.files.makeDir(MOUNT_DIRECTORY)

  // 创建 credentials 文件
  // s3fs 会默认从 /root/.passwd-s3fs 中读取 AWS 访问密钥信息
  await sandbox.files.write('/root/.passwd-s3fs', `${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}`)

  // 设置 credentials 文件的权限
  await sandbox.commands.run('sudo chmod 600 /root/.passwd-s3fs')

  // 挂载 Amazon S3 Bucket 到指定目录
  // 常用挂载参数：
  // - allow_other: 允许其他用户访问挂载点
  // - endpoint: 指定 S3 bucket 的所在区域
  // 详细参数请参考：https://manpages.ubuntu.com/manpages/noble/en/man1/s3fs.1.html
  const mountOptions = 'allow_other,endpoint=us-east-1'
  await sandbox.commands.run(`sudo s3fs ${AWS_BUCKET_NAME} ${MOUNT_DIRECTORY} -o ${mountOptions}`)

  // 测试写入文件
  await sandbox.files.write(`${MOUNT_DIRECTORY}/test-file.txt`, 'test-file-content')

  // 测试读取文件
  const content = await sandbox.files.read(`${MOUNT_DIRECTORY}/test-file.txt`)
  console.log(content)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={null}
  import os
  from ppio_sandbox.core import Sandbox

  TEMPLATE_ID = os.environ.get("PPIO_TEMPLATE_ID")
  AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID")
  AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")
  AWS_BUCKET_NAME = os.environ.get("AWS_BUCKET_NAME")
  if not TEMPLATE_ID or not AWS_ACCESS_KEY_ID or not AWS_SECRET_ACCESS_KEY or not AWS_BUCKET_NAME:
      raise Exception("environment variable PPIO_TEMPLATE_ID or AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY or AWS_BUCKET_NAME is not set")

  MOUNT_DIRECTORY = "/mnt/s3-bucket"

  sandbox = Sandbox.create(TEMPLATE_ID)

  # 创建挂载目录
  sandbox.files.make_dir(MOUNT_DIRECTORY)

  # 创建 credentials 文件
  # s3fs 会默认从 /root/.passwd-s3fs 中读取 AWS 访问密钥信息，如果使用其他路径，需要在 s3fs 命令中添加该路径。
  sandbox.files.write("/root/.passwd-s3fs", f"{AWS_ACCESS_KEY_ID}:{AWS_SECRET_ACCESS_KEY}")

  # 设置 credentials 文件的权限
  sandbox.commands.run("sudo chmod 600 /root/.passwd-s3fs")

  # 挂载 Amazon S3 Bucket 到指定目录
  # 重要参数说明：
  # - allow_other: 允许其他用户访问挂载点
  # - endpoint: 指定 S3 bucket 的所在区域
  # 详细参数请参考：https://manpages.ubuntu.com/manpages/noble/en/man1/s3fs.1.html
  mount_options = "allow_other,endpoint=cn-north-1"
  sandbox.commands.run(f"sudo s3fs {AWS_BUCKET_NAME} {MOUNT_DIRECTORY} -o {mount_options}")

  # 测试写入文件
  write_result = sandbox.files.write(f"{MOUNT_DIRECTORY}/test-file.txt", "test-file-content")

  # 测试读取文件
  content = sandbox.files.read(f"{MOUNT_DIRECTORY}/test-file.txt")
  print("File content:", content)

  sandbox.kill()
  ```
</CodeGroup>

## 阿里云对象存储（OSS）

可以使用 [ossfs](https://help.aliyun.com/zh/oss/developer-reference/install-ossfs-1-0) 工具将阿里云对象存储（OSS）Bucket 挂载到沙箱中。

以下是一个 `ppio.Dockerfile` 示例，展示了如何在制作 [沙箱模板](/sandbox/sandbox-template) 时安装 ossfs 工具。

<CodeGroup>
  ```dockerfile ppio.Dockerfile icon="docker" theme={null}
  # You can use most Debian-based base images
  FROM ubuntu:latest

  # Install dependencies and customize sandbox
  RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
      apt-get install -y wget gdebi-core && \
      # 从这里复制常用系统的软件包安装地址：https://help.aliyun.com/zh/oss/developer-reference/install-ossfs-1-0
      wget https://gosspublic.alicdn.com/ossfs/ossfs_1.91.8_ubuntu24.04_amd64.deb &&\
      gdebi -n ossfs_1.91.8_ubuntu24.04_amd64.deb && \
      rm ossfs_1.91.8_ubuntu24.04_amd64.deb
  ```
</CodeGroup>

下面展示了如何在沙箱实例中通过 ossfs 来挂载阿里云对象存储（OSS）Bucket。

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={null}
  import { Sandbox } from 'ppio-sandbox'

  const TEMPLATE_ID = process.env.PPIO_OSS_TEMPLATE_ID
  const OSS_ACCESS_KEY_ID = process.env.OSS_ACCESS_KEY_ID
  const OSS_ACCESS_KEY_SECRET = process.env.OSS_ACCESS_KEY_SECRET
  const OSS_BUCKET_NAME = process.env.OSS_BUCKET_NAME
  if (!TEMPLATE_ID || !OSS_ACCESS_KEY_ID || !OSS_ACCESS_KEY_SECRET || !OSS_BUCKET_NAME) {
      throw new Error('environment variable PPIO_OSS_TEMPLATE_ID or OSS_ACCESS_KEY_ID or OSS_ACCESS_KEY_SECRET or OSS_BUCKET_NAME is not set')
  }

  const MOUNT_DIRECTORY = "/mnt/oss-bucket"

  const sandbox = await Sandbox.create(TEMPLATE_ID)

  // 创建挂载目录
  await sandbox.files.makeDir(MOUNT_DIRECTORY)

  // 创建 credentials 文件
  // ossfs 会默认从 /etc/passwd-ossfs 中读取 OSS 访问密钥信息
  // 如果使用其他路径，可以使用 -o passwd_file 参数指定自定义配置文件的路径
  // 更多参数请参考：https://help.aliyun.com/zh/oss/developer-reference/common-options
  await sandbox.files.write('/etc/passwd-ossfs', `${OSS_BUCKET_NAME}:${OSS_ACCESS_KEY_ID}:${OSS_ACCESS_KEY_SECRET}`)

  // 设置 credentials 文件的权限
  await sandbox.commands.run('sudo chmod 600 /etc/passwd-ossfs')

  // 挂载 OSS Bucket 到指定目录
  // -o url 必须和 Bucket 的所在区域一致，参考：https://help.aliyun.com/zh/oss/regions-and-endpoints 查看更多区域信息。
  await sandbox.commands.run(`sudo ossfs ${OSS_BUCKET_NAME} ${MOUNT_DIRECTORY} -o allow_other -o passwd_file=/etc/passwd-ossfs -o url=https://oss-cn-beijing.aliyuncs.com`)

  // 注意：此处不能通过 sandbox.files.write 方法创建文件。由于 ossfs 将挂载点视为一个 "虚拟的" 超大磁盘空间，sandbox.files.write 方法会尝试获取实际磁盘使用情况并检查空间是否足够，但该信息在虚拟挂载下容易溢出或异常，进而导致误报 "磁盘空间不足" 错误。
  // 测试写入文件
  await sandbox.commands.run(`echo "test-file-content" > ${MOUNT_DIRECTORY}/test-file.txt`)

  // 测试读取文件
  const result = await sandbox.commands.run(`cat ${MOUNT_DIRECTORY}/test-file.txt`)
  console.log(result)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={null}
  import os
  from ppio_sandbox.core import Sandbox

  TEMPLATE_ID = os.environ.get("PPIO_OSS_TEMPLATE_ID")
  OSS_ACCESS_KEY_ID = os.environ.get("OSS_ACCESS_KEY_ID")
  OSS_ACCESS_KEY_SECRET = os.environ.get("OSS_ACCESS_KEY_SECRET")
  OSS_BUCKET_NAME = os.environ.get("OSS_BUCKET_NAME")
  if not TEMPLATE_ID or not OSS_ACCESS_KEY_ID or not OSS_ACCESS_KEY_SECRET or not OSS_BUCKET_NAME:
      raise Exception("environment variable PPIO_TEMPLATE_ID or OSS_ACCESS_KEY_ID or OSS_ACCESS_KEY_SECRET or OSS_BUCKET_NAME is not set")

  MOUNT_DIRECTORY = "/mnt/oss-bucket"

  sandbox = Sandbox.create(TEMPLATE_ID)

  # 创建挂载目录
  sandbox.files.make_dir(MOUNT_DIRECTORY)

  # 创建 credentials 文件
  # ossfs 会默认从 /etc/passwd-ossfs 中读取 OSS 访问密钥信息
  # 如果使用其他路径，可以使用 -o passwd_file 参数指定自定义配置文件的路径
  # 更多参数请参考：https://help.aliyun.com/zh/oss/developer-reference/common-options
  sandbox.files.write("/etc/passwd-ossfs", f'{OSS_BUCKET_NAME}:{OSS_ACCESS_KEY_ID}:{OSS_ACCESS_KEY_SECRET}')

  # 设置 credentials 文件的权限
  sandbox.commands.run("sudo chmod 600 /etc/passwd-ossfs")

  # 挂载 OSS Bucket 到指定目录
  # -o url 必须和 Bucket 的所在区域一致，参考：https://help.aliyun.com/zh/oss/regions-and-endpoints 查看更多区域信息。
  sandbox.commands.run(f"sudo ossfs {OSS_BUCKET_NAME} {MOUNT_DIRECTORY} -o allow_other -o passwd_file=/etc/passwd-ossfs -o url=https://oss-cn-beijing.aliyuncs.com")

  # 注意：此处不能通过 sandbox.files.write 方法创建文件。由于 ossfs 将挂载点视为一个 "虚拟的" 超大磁盘空间，sandbox.files.write 方法会尝试获取实际磁盘使用情况并检查空间是否足够，但该信息在虚拟挂载下容易溢出或异常，进而导致误报 "磁盘空间不足" 错误。
  # 测试写入文件
  sandbox.commands.run(f"echo 'test-file-content' > {MOUNT_DIRECTORY}/test-file.txt")

  # 测试读取文件
  result = sandbox.commands.run(f"cat {MOUNT_DIRECTORY}/test-file.txt")
  print("File content:", result)

  sandbox.kill()
  ```
</CodeGroup>

## 腾讯云对象存储（COS）

可以使用 [GooseFS-Lite](https://cloud.tencent.com/document/product/436/115507) 工具将腾讯云对象存储（COS）Bucket 挂载到沙箱中。

以下是一个 `ppio.Dockerfile` 示例，展示了如何在制作 [沙箱模板](/sandbox/sandbox-template) 时安装 GooseFS-Lite 工具。

<CodeGroup>
  ```dockerfile ppio.Dockerfile icon="docker" theme={null}
  # You can use most Debian-based base images
  FROM ubuntu:latest

  # Install dependencies and customize sandbox
  RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
      apt install -y curl libfuse-dev wget && \
      curl -fssL https://downloads.tencentgoosefs.cn/goosefs-lite/install.sh | sh -x && \
      cd goosefs-lite-* && \
      bash bin/install.sh && \
      bash bin/install-jdk.sh https://github.com/Tencent/TencentKona-11/releases/download/kona11.0.22/TencentKona-11.0.22.b1-jdk_linux-x86_64.tar.gz
  ```
</CodeGroup>

下面展示了如何在沙箱实例中通过 GooseFS-Lite 来挂载腾讯云对象存储（COS）Bucket。

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={null}
  import { Sandbox } from 'ppio-sandbox'

  const TEMPLATE_ID = process.env.PPIO_COS_TEMPLATE_ID
  const COS_ACCESS_KEY_ID = process.env.COS_ACCESS_KEY_ID
  const COS_SECRET_ACCESS_KEY = process.env.COS_SECRET_ACCESS_KEY
  const COS_BUCKET_NAME = process.env.COS_BUCKET_NAME
  const COS_BUCKET_REGION = process.env.COS_BUCKET_REGION
  if (!TEMPLATE_ID || !COS_ACCESS_KEY_ID || !COS_SECRET_ACCESS_KEY || !COS_BUCKET_NAME || !COS_BUCKET_REGION) {
      throw new Error('environment variable PPIO_COS_TEMPLATE_ID or COS_ACCESS_KEY_ID or COS_SECRET_ACCESS_KEY or COS_BUCKET_NAME or COS_BUCKET_REGION is not set')
  }

  const MOUNT_DIRECTORY = "/mnt/cos-bucket"

  const sandbox = await Sandbox.create(TEMPLATE_ID)

  // 创建挂载目录
  await sandbox.files.makeDir(MOUNT_DIRECTORY)

  // 修改 conf/core-site.xml 文件
  // 配置 COS Bucket 的访问密钥和区域
  // 更具可读性和安全性，避免字符串拼接并用模板字符串实现，且一条命令一行方便维护
  const cmdUpdateCoreSiteXml = [
      `sudo sed -i "/<name>fs.cosn.userinfo.secretId<\\/name>/{N;s/<value>[^<]*<\\/value>/<value>${COS_ACCESS_KEY_ID}<\\/value>/}" conf/core-site.xml`,
      `sudo sed -i "/<name>fs.cosn.userinfo.secretKey<\\/name>/{N;s/<value>[^<]*<\\/value>/<value>${COS_SECRET_ACCESS_KEY}<\\/value>/}" conf/core-site.xml`,
      `sudo sed -i "/<name>fs.cosn.bucket.region<\\/name>/{N;s/<value>[^<]*<\\/value>/<value>${COS_BUCKET_REGION}<\\/value>/}" conf/core-site.xml`
  ].join('; ');

  // 更新 core-site.xml 文件
  await sandbox.commands.run(`cd /goosefs-lite-* && (${cmdUpdateCoreSiteXml})`)

  // 挂载 COS Bucket 到指定目录
  await sandbox.commands.run(`cd /goosefs-lite-* && sudo ./bin/goosefs-lite mount -o "allow_other" ${MOUNT_DIRECTORY} cosn://${COS_BUCKET_NAME}/`)

  // 测试写入文件
  await sandbox.files.write(`${MOUNT_DIRECTORY}/test-file.txt`, 'test-file-content')

  // 测试读取文件
  const content = await sandbox.files.read(`${MOUNT_DIRECTORY}/test-file.txt`)
  console.log(content)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={null}
  import os
  from ppio_sandbox.core import Sandbox

  TEMPLATE_ID = os.environ.get("PPIO_COS_TEMPLATE_ID")
  COS_ACCESS_KEY_ID = os.environ.get("COS_ACCESS_KEY_ID")
  COS_SECRET_ACCESS_KEY = os.environ.get("COS_SECRET_ACCESS_KEY")
  COS_BUCKET_NAME = os.environ.get("COS_BUCKET_NAME")
  COS_BUCKET_REGION = os.environ.get("COS_BUCKET_REGION")
  if not TEMPLATE_ID or not COS_ACCESS_KEY_ID or not COS_SECRET_ACCESS_KEY or not COS_BUCKET_NAME or not COS_BUCKET_REGION:
      raise Exception(
          "environment variable PPIO_COS_TEMPLATE_ID or COS_ACCESS_KEY_ID or COS_SECRET_ACCESS_KEY or COS_BUCKET_NAME or COS_BUCKET_REGION is not set")

  MOUNT_DIRECTORY = "/mnt/cos-bucket"

  sandbox = Sandbox.create(TEMPLATE_ID)

  # 创建挂载目录
  sandbox.files.make_dir(MOUNT_DIRECTORY)

  # 修改 conf/core-site.xml 文件
  # 配置 COS Bucket 的访问密钥和区域
  cmd_update_core_site_xml = (
      "sudo sed -i '/<name>fs.cosn.userinfo.secretId<\/name>/{N;s/<value>[^<]*<\/value>/<value>" +
      COS_ACCESS_KEY_ID+"<\/value>/}' conf/core-site.xml;"
      "sudo sed -i '/<name>fs.cosn.userinfo.secretKey<\/name>/{N;s/<value>[^<]*<\/value>/<value>" +
      COS_SECRET_ACCESS_KEY+"<\/value>/}' conf/core-site.xml;"
      "sudo sed -i '/<name>fs.cosn.bucket.region<\/name>/{N;s/<value>[^<]*<\/value>/<value>" +
      COS_BUCKET_REGION+"<\/value>/}' conf/core-site.xml"
  )

  # 更新 core-site.xml 文件
  sandbox.commands.run(f"cd /goosefs-lite-* && ({cmd_update_core_site_xml})")

  # 挂载 COS Bucket 到指定目录
  sandbox.commands.run(f"cd /goosefs-lite-* && sudo ./bin/goosefs-lite mount -o \"allow_other\" {MOUNT_DIRECTORY} cosn://{COS_BUCKET_NAME}/")

  # 测试写入文件
  sandbox.files.write(f"{MOUNT_DIRECTORY}/test-file.txt", "test-file-content")

  # 测试读取文件
  sandbox.files.read(f"{MOUNT_DIRECTORY}/test-file.txt")

  sandbox.kill()
  ```
</CodeGroup>
