DaytonaDocs

Secrets

Store credentials once and attach them safely to sandboxes.

Secrets are project-scoped named values — API keys, tokens, service credentials — stored encrypted at rest and attached to sandboxes by name. They exist to solve a specific problem: sandboxes often run untrusted or agent-generated code that needs to use a credential (call the OpenAI API, push to a service) without ever being able to read it.

Daytona's secrets are write-only: a value can be set or replaced, but no API ever returns it, and — more importantly — the real value never enters the sandbox at all. Code inside the sandbox sees an environment variable containing an opaque placeholder; when the sandbox makes an outbound request, the platform substitutes the real value into the request on the way out, restricted to the domains you allow. Even code that prints every environment variable and inspects every byte of its filesystem cannot recover the actual credential.

Managing secrets

Secrets are managed through daytona.secret in the SDKs, the /secrets REST routes, and the Secrets page in the dashboard. Setting a secret is an upsert — the same call creates it or replaces its value.

Python
from rlp import Daytona
 
daytona = Daytona()
 
daytona.secret.set(
    "OPENAI_API_KEY",
    "sk-proj-...",
    allowed_domains=["api.openai.com"],
    description="OpenAI key for agent sandboxes",
)
 
secrets = daytona.secret.list()          # metadata only, never values
meta = daytona.secret.get("OPENAI_API_KEY")
daytona.secret.delete("OPENAI_API_KEY")
TypeScript
import { Daytona } from '@rlp/sdk'
 
const daytona = new Daytona()
 
await daytona.secret.set('OPENAI_API_KEY', 'sk-proj-...', {
  allowedDomains: ['api.openai.com'],
  description: 'OpenAI key for agent sandboxes',
})
 
const secrets = await daytona.secret.list()
const meta = await daytona.secret.get('OPENAI_API_KEY')
await daytona.secret.delete('OPENAI_API_KEY')
cURL
# Create or replace (returns 204)
curl -X PUT https://api.rl.trydaytona.com/secrets/OPENAI_API_KEY \
  -H "Authorization: Bearer $RLP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": "sk-proj-...", "allowed_domains": ["api.openai.com"]}'
 
# List / inspect metadata (name, allowed_domains, description, updated_at)
curl https://api.rl.trydaytona.com/secrets \
  -H "Authorization: Bearer $RLP_API_KEY"
curl https://api.rl.trydaytona.com/secrets/OPENAI_API_KEY \
  -H "Authorization: Bearer $RLP_API_KEY"
 
# Delete (idempotent, 204)
curl -X DELETE https://api.rl.trydaytona.com/secrets/OPENAI_API_KEY \
  -H "Authorization: Bearer $RLP_API_KEY"

Validation rules:

  • Names double as the injected environment variable name, so they must match [A-Za-z_][A-Za-z0-9_]* (letters, digits, underscores; no leading digit), up to 256 characters. OPENAI_API_KEY is valid; openai-key is not.
  • Values may be up to 64 KiB.
  • allowed_domains entries are hostnames — exact (api.openai.com) or leading-wildcard (*.openai.com). Schemes, paths, ports, and empty entries are rejected. An empty list places no host restriction on where the secret may be substituted; for real credentials you should always scope it.

Using secrets in sandboxes

Attach secrets by name at sandbox creation with the secrets parameter. Names that don't exist in the project fail the create with 400 Bad Request before anything is launched.

Python
from rlp import CreateSandboxFromImageParams, Daytona
 
daytona = Daytona()
sandbox = daytona.create(CreateSandboxFromImageParams(
    image="python:3.12-slim",
    secrets=["OPENAI_API_KEY"],
))
 
# Inside the sandbox: use the env var as if it were the real key.
sandbox.process.exec(
    'python -c "import os, urllib.request; ..."'  # code reads os.environ["OPENAI_API_KEY"]
)
TypeScript
const sandbox = await daytona.create({
  image: 'python:3.12-slim',
  secrets: ['OPENAI_API_KEY'],
})
cURL
curl -X POST https://api.rl.trydaytona.com/vms \
  -H "Authorization: Bearer $RLP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image": "python:3.12-slim", "secrets": ["OPENAI_API_KEY"]}'

Inside the sandbox, $OPENAI_API_KEY contains a unique per-sandbox placeholder token — not the key. Your code uses it exactly as it would use the real value (set it as a bearer header, pass it to a client library); when the request leaves the sandbox toward a host in allowed_domains, the platform swaps in the real value. Requests to hosts outside the allowlist carry only the placeholder, which is useless anywhere else.

Note: because substitution happens on outbound traffic, secrets work for credentials sent over the network (API keys, bearer tokens). They are not a mechanism for values your code must process locally, such as decryption keys.

Rotation and lifecycle

  • Rotation is a replace-in-place: set the same name with a new value. Sandboxes created afterwards use the new value; sandboxes already running keep the value they started with until they are deleted.
  • Deleting a secret is idempotent and likewise does not affect sandboxes already running with it attached.
  • Use one secret per environment or tenant (OPENAI_API_KEY_STAGING, OPENAI_API_KEY_PROD) rather than swapping one name back and forth — attachment by name keeps sandbox creation code declarative.

Secrets and object stores

Object stores build on secrets: the S3 access key and secret key backing a bucket connection are stored as project secrets and referenced by name. These credential secrets are hidden from the default secrets list (they are platform-managed); pass ?include_object_store=true on GET /secrets, or use the dashboard's toggle, to see them.

On this page