Skip to main content
Configure agent environment

Mount your own OSS buckets (Beta)

Declare your own OSS buckets on an Environment and mount them at /mnt/buckets/<name> in the Session sandbox.

Declare your own Alibaba Cloud OSS buckets in an Environment's config.storage.buckets[]. Sessions created with that Environment mount each bucket (or one prefix of it) at /mnt/buckets/<name>, and the Agent can read and write it like a local directory. Bucket mounts are environment-level configuration: a Session snapshots the configuration when it is created, and later Environment changes do not affect existing Sessions.
  • An Environment can declare at most 8 buckets.
  • Only Alibaba Cloud OSS is supported (provider: "aliyun-oss").
  • Both read-only and read-write are supported.
  • Reads download from OSS through a presigned URL; writes upload directly to OSS with multipart upload (writes smaller than 1 MiB are relayed by Qoder).
  • Your RAM role or AccessKey never enters the sandbox.
This feature is in Beta. Whenever the resulting config.storage.buckets is non-empty on create or update, the request must carry x-qoder-beta: oss-bucket-2026-09-23. Adding buckets to an Environment for the first time also requires the account to be eligible for the Beta (allowlist or full rollout); otherwise the request returns 503 feature_not_available. Removing buckets (the result contains none) does not require the header.

Configuration fields

config.storage accepts a single field, buckets, and is only valid for cloud Environments:
{
  "type": "cloud",
  "storage": {
    "buckets": []
  }
}
Fields of one bucket:
FieldTypeRequiredDescription
namestringYesMount directory name, i.e. /mnt/buckets/<name>. 1–63 characters of letters, digits, ., _, -, starting with a letter or digit; unique within the Environment
providerstringYesAlways "aliyun-oss"
regionstringYesOSS region ID such as cn-hangzhou or ap-southeast-1. Only a region ID is accepted; custom endpoints are not supported
bucketstringYesBucket name; must not contain /, *, or ?
prefixstringNoMount only the objects under this prefix. Leading and trailing / are ignored; omitted means the whole bucket. Must not contain *, ?, \, NUL, or empty, ., or .. segments
accessstringNoread_only (default) or read_write
accelerator_endpointstringNoOSS accelerator host in the form <label>.oss-data-acc.aliyuncs.com; use only when the bucket is in the sandbox's region
credentialobjectYesCredentials for the bucket; see Credentials
Within one request, name must be unique, and two buckets with the same region + bucket + prefix are rejected. Request fields are validated strictly: unknown fields, keys with different casing, and wrong value types return 400.

Example

Create an Environment with two buckets:
curl -s -X POST "https://api.qoder.com.cn/api/v1/cloud/environments" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "x-qoder-beta: oss-bucket-2026-09-23" \
  -d '{
    "name": "with-oss-buckets",
    "config": {
      "type": "cloud",
      "storage": {
        "buckets": [
          {
            "name": "datasets",
            "provider": "aliyun-oss",
            "region": "cn-hangzhou",
            "bucket": "my-bucket",
            "prefix": "team/data/",
            "access": "read_only",
            "credential": {
              "type": "ram_role",
              "role_arn": "acs:ram::1234567890123456:role/qoder-oss-mount"
            }
          },
          {
            "name": "outputs",
            "provider": "aliyun-oss",
            "region": "cn-hangzhou",
            "bucket": "my-bucket",
            "prefix": "team/outputs/",
            "access": "read_write",
            "credential": {
              "type": "access_key",
              "access_key_id": "LTAI5tExampleAccessKey",
              "access_key_secret": "ExampleSecret"
            }
          }
        ]
      }
    }
  }'
Credentials are returned in their read view only:
{
  "id": "env_019e44eb66bb748cabcd1489f6fa4428",
  "type": "environment",
  "name": "with-oss-buckets",
  "description": "",
  "config": {
    "type": "cloud",
    "storage": {
      "buckets": [
        {
          "name": "datasets",
          "provider": "aliyun-oss",
          "region": "cn-hangzhou",
          "bucket": "my-bucket",
          "prefix": "team/data/",
          "access": "read_only",
          "credential": {
            "type": "ram_role"
          }
        },
        {
          "name": "outputs",
          "provider": "aliyun-oss",
          "region": "cn-hangzhou",
          "bucket": "my-bucket",
          "prefix": "team/outputs/",
          "access": "read_write",
          "credential": {
            "type": "access_key",
            "access_key_id": "LTAI****sKey"
          }
        }
      ]
    }
  },
  "metadata": {},
  "archived_at": null,
  "created_at": "2026-10-21T08:00:00.000000Z",
  "updated_at": "2026-10-21T08:00:00.000000Z"
}

Credentials

credential.type is either ram_role (recommended) or access_key.
  1. Create a RAM role in your Alibaba Cloud account for Qoder to assume. Its trust policy must allow the Qoder service role to assume it; contact support for the Qoder service role.
  2. Restrict who can assume it with sts:ExternalId:
Session ownersts:ExternalId
Individual userUser ID
Enterprise (service account)<organization ID>@<workspace ID>
{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "RAM": ["<Qoder service role ARN>"]
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": ["<your user ID or organization ID@workspace ID>"]
        }
      }
    }
  ]
}
  1. Put the role ARN in credential.role_arn.
Qoder also attaches a session policy that narrows the credentials to the declared bucket, prefix, and access mode. The role itself must also grant the actions in Permissions; the effective permission is the intersection of the two.

AccessKey

Use the AccessKey of a dedicated RAM user, never the account's root AccessKey, and grant that user the policy below.
  • The AccessKey is stored with the Environment configuration and snapshotted into Sessions created afterwards.
  • Reading an Environment returns only a masked access_key_id (for example LTAI****sKey); access_key_secret is never returned.
  • Rotating the AccessKey only affects Sessions created afterwards.
  • There is no STS session policy: Qoder enforces the prefix and read-only limits, while the storage-side limit is the AccessKey's own RAM policy.

Keeping credentials on update

config is replaced as a whole, but storage.buckets[].credential is write-only and preserved per bucket name:
  • Omitting a bucket's credential, or echoing the masked credential you read back, keeps the stored credential.
  • Renaming a bucket creates a new bucket and requires a full credential.
  • Changing credential.type or sending "credential": null requires a full new credential.
  • Removing a bucket from the array, or sending "buckets": [], removes it from Sessions created afterwards; existing Sessions keep their snapshot.

Authorizing a bucket

The bucket owner must explicitly allow Qoder to mount it. Authorization consists of bucket tags and an optional access list file; at least one of them must name who may mount, otherwise the mount is refused.

Tags

Add tags to the bucket in the OSS console. You can find your user ID, organization ID, and workspace ID in the Qoder console:
TagRequiredDescription
qoder-mountYesMust be enabled, allowing Qoder to mount the bucket
qoder-mount-usersNoComma-separated individual user IDs allowed to mount
qoder-mount-organizationsNoComma-separated organization IDs allowed to mount
qoder-mount-workspacesNoComma-separated workspace IDs allowed to mount
Rules:
  • A tag value is limited to 128 characters; use tags only for a small number of IDs.
  • Individual sessions match only qoder-mount-users; enterprise sessions match only qoder-mount-organizations. They are different namespaces, so an identical spelling does not grant access across them.
  • When qoder-mount-workspaces is set, the Session's workspace must match.
  • When any restricting tag is present, every dimension that is set must match.
  • Tags are cached for about 1 minute; on read failures the previous value is used for up to 10 minutes; an explicit denial (403) takes effect immediately.

Access list (optional)

For finer control by Agent or Session, put an access list file next to the mounted directory. It lives outside the mounted directory and is neither visible nor writable from the sandbox.
  • Name: <prefix without the trailing slash>.qoder-acl.json. For example, a prefix of team/data/ uses team/data.qoder-acl.json.
  • A whole-bucket mount (no prefix) has no access list and relies on tags only.
  • Only the file at the mounted directory's own level is read; parent directories are not consulted.
{
  "version": 1,
  "grants": [
    {"users": ["<user ID>"]},
    {"organizations": ["<organization ID>"], "workspaces": ["<workspace ID>"], "agents": ["<agent ID>"], "sessions": ["<session ID>"]}
  ]
}
Rules:
  • The supported dimensions are users, organizations, workspaces, agents, and sessions.
  • Within one grant, every dimension that is set must match; unset dimensions are unrestricted; an empty list matches nobody.
  • Any matching grant allows the mount; if none matches, the mount is refused with 403.
  • When both tags and an access list are present, both must match.
  • A file larger than 64 KB, invalid version 1 JSON, unknown keys, a null grant, or a dimension that is not an array of non-empty strings is rejected.
  • agents and sessions are checked at mount time, not when the Environment is saved.
  • The list is cached for about 1 minute; after it expires, read failures reuse the previous value for up to 10 minutes; 403 is never reused.
  • A missing file is not an error, but in that case the tags must name who may mount.

Permissions

Grant the RAM role or RAM user the minimum permissions:
ActionPurposeNeeded by
oss:ListObjectsList directoriesRead-only / read-write
oss:GetBucketTaggingRead bucket tags (mount authorization)Read-only / read-write
oss:GetObjectRead files; read the access list; read sources during renameRead-only / read-write
oss:HeadObjectStat filesRead-only / read-write
oss:PutObjectWrite files, multipart upload, create directories, write rename targetsRead-write only
oss:DeleteObjectDelete files and empty directories, delete rename sourcesRead-write only
oss:AbortMultipartUploadClean up interrupted uploadsRead-write only
oss:ListPartsVerify parts before completing an upload; without it large writes failRead-write only
Example read-only policy (prefix is team/data/):
{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["oss:ListObjects", "oss:GetBucketTagging"],
      "Resource": ["acs:oss:*:*:my-bucket"]
    },
    {
      "Effect": "Allow",
      "Action": ["oss:GetObject", "oss:HeadObject"],
      "Resource": ["acs:oss:*:*:my-bucket/team/data/*"]
    },
    {
      "Effect": "Allow",
      "Action": ["oss:GetObject"],
      "Resource": ["acs:oss:*:*:my-bucket/team/data.qoder-acl.json"]
    }
  ]
}
A read-write mount also needs oss:PutObject, oss:DeleteObject, oss:AbortMultipartUpload, and oss:ListParts in the second statement's Action.

Validation when saving

When an Environment is saved (on create, or when an update adds or changes a bucket), Qoder tries the mount as the saving user with the submitted credentials:
  1. Obtain credentials (assume the role or use the AccessKey).
  2. Read the bucket tags.
  3. Read the access list, if present.
  4. List the prefix once (max-keys=1).
Only new or changed buckets are checked; unchanged buckets are not checked again. Validation times out after 15 seconds.
  • A configuration or permission refusal returns 400 invalid_request_error with a message such as Bucket '<name>' cannot be mounted: <reason>.
  • An OSS / STS outage returns 503 api_error and can be retried.
  • The agents / sessions dimensions of the access list are not checked when saving; they are checked at mount time.

Behavior in the sandbox

Mount point: /mnt/buckets/<name>. With access: read_only it is a read-only filesystem; with read_write files can be created, modified, and deleted.
  • Directories: object storage has no real directories. A directory is the prefix of object keys; an empty directory is kept alive by a zero-byte <path>/ placeholder object, the same convention as the OSS console and ossutil.
  • Reads: Qoder returns a 302 to a presigned URL and the sandbox downloads directly from OSS.
  • Writes: files smaller than 1 MiB use a regular upload relayed by Qoder; files of 1 MiB or more use multipart upload (the sandbox PUTs each part to a presigned URL and then commits).
  • Rename: a single-file rename is copy + delete and is not atomic. Concurrent Sessions modifying the same path can lose writes; Qoder does not arbitrate across Sessions.
  • Directory rename: not natively supported; the client falls back to moving files one by one.
  • Deleting directories: a non-empty directory returns 409; delete its files first.
  • Listing limit: a single directory with more than 100,000 entries returns 507 rather than a truncated listing.
  • File size limit: a direct upload is at most 10,000 parts × 64 MiB, i.e. 625 GiB; the bucket's own quotas and limits still apply.
  • Interrupted uploads: an interrupted write leaves unfinished parts in the bucket. Configure a lifecycle rule on the bucket to clean them up.
  • Regions: when the bucket is in the sandbox's region, the OSS internal endpoint is used; otherwise traffic goes over the public endpoint with higher latency. The OSS accelerator works only in the same region.
  • Networking: the sandbox accesses OSS directly for reads and for writes of 1 MiB or more. If the Environment uses restricted networking, allow the corresponding OSS domain.

Errors

HTTPtypeDescription
400invalid_request_errorInvalid bucket field, missing credential, missing x-qoder-beta header, or the save-time probe was refused
401authentication_errorPAT or SAT invalid or expired
403permission_errorNot authorized for the target Environment
503feature_not_availableThe account is not eligible for the oss_bucket Beta
503api_errorOSS / STS was temporarily unavailable during the save-time probe; retry
Refusals at mount time (bucket tags do not authorize the caller, the access list does not match, the credentials lack permissions, and so on) surface as EACCES in the sandbox and are not returned by the Environment API. Check the bucket tags, access list, and RAM policy first.

Limitations

  • Only Alibaba Cloud OSS is supported; S3, COS, MinIO, and custom endpoints are not.
  • Qoder does not manage object versions, lifecycle rules, bucket ACLs, or other OSS settings.
  • One prefix of one bucket can appear only once per Environment; to mount several directories of the same bucket, use different name and prefix values.
  • Mounts use the Environment snapshot; changing an Environment does not alter existing Sessions.