Secrets
Secrets are a secure way to store and use sensitive values such as API keys, tokens, and passwords across your Daytona sandboxes. They let code running in a sandbox authenticate to external services without hardcoding credentials into your source, snapshots, or sandbox environment variables.
Secrets are organization-scoped, encrypted credentials that Daytona injects into a sandbox’s outbound HTTP(S) traffic without ever placing the plaintext inside the sandbox. Instead of an environment variable holding the actual API key, the sandbox holds an opaque placeholder token. An outbound proxy replaces the placeholder with the real value before the request reaches its destination, and only when the request goes to a host you have allowed.
This lets you give a sandbox access to a credential such as an LLM API key or a database password while keeping the plaintext out of the sandbox environment, file system, process arguments, and logs. Code running in the sandbox can use the credential to reach an approved host, but cannot read the value or send it anywhere else.
How secrets work
Section titled “How secrets work”A secret never enters the sandbox in plaintext. Daytona uses an opaque placeholder token and an outbound proxy to swap the placeholder for the real value at request time:
- You store a secret in your organization. Daytona encrypts the value at rest and assigns it an opaque placeholder token, for example
dtn_secret_<random_string>. - When you create a sandbox, you map an environment variable to a secret by name. Daytona sets that environment variable to the placeholder, not to the real value.
- When the sandbox makes an outbound HTTP(S) request, the proxy inspects it. If the request carries a placeholder and the destination host matches the secret’s allowlist, the proxy replaces the placeholder with the decrypted value before the request reaches its destination.
- For any other destination, the placeholder is forwarded unchanged. The real value is never sent to a host you did not approve.
Because the substitution happens in the proxy, the plaintext value is never present inside the sandbox. The sandbox sees the placeholder in its environment, and any request to a non-allowed host carries the harmless placeholder rather than the secret.
A secret has the following fields:
| Field | Description |
|---|---|
name | Unique name within the organization. Used to reference the secret when creating a sandbox. |
value | The plaintext value. Encrypted at rest and never returned by the API after creation. |
description | Optional human-readable description. |
hosts | Allowlist of hosts the secret may be sent to. Supports exact hosts and *. wildcards. |
placeholder | Opaque token Daytona generates for the secret. This is the value injected into the sandbox environment. |
Host allowlist
Section titled “Host allowlist”The host allowlist is the set of hosts a secret may be sent to. Set your allowed hosts using the hosts array when creating a secret or updating a secret. The proxy replaces the placeholder only for requests whose destination host matches an entry in the allowlist; a request to any other host carries the unmodified placeholder.
- Hosts only: use hostnames; omit protocols, paths, ports, or query strings
- Wildcards supported: prefix a host with
*.to allow the base domain and its subdomains - Case-insensitive: host matching ignores letter case
- Scope tightly: list only the hosts the secret requires
The following examples are valid:
- Single host:
["api.example.com"] - Wildcard host:
["*.example.com"] - Multiple hosts:
["api.example.com", "*.example.org", "service.example.net"]
Create a secret
Section titled “Create a secret”Daytona provides methods to create secrets.
from daytona import CreateSecretParams, Daytona
daytona = Daytona()
secret = daytona.secret.create(CreateSecretParams( name="my-secret", value="secret-value", description="Optional description", hosts=["api.example.com"],))import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()
const secret = await daytona.secret.create({ name: 'my-secret', value: 'secret-value', description: 'Optional description', hosts: ['api.example.com'],})require 'daytona'
daytona = Daytona::Daytona.new
secret = daytona.secret.create( 'my-secret', 'secret-value', description: 'Optional description', hosts: ['api.example.com'])package main
import ( "context" "log"
"github.com/daytonaio/daytona/libs/sdk-go/pkg/daytona" "github.com/daytonaio/daytona/libs/sdk-go/pkg/types")
func main() { client, err := daytona.NewClient() if err != nil { log.Fatal(err) }
description := "Optional description" secret, err := client.Secret.Create(context.Background(), &types.CreateSecretParams{ Name: "my-secret", Value: "secret-value", Description: &description, Hosts: []string{"api.example.com"}, }) if err != nil { log.Fatal(err) } log.Printf("Secret ID: %s", secret.ID)}import io.daytona.sdk.Daytona;import io.daytona.sdk.model.CreateSecretParams;import io.daytona.sdk.model.Secret;
import java.util.List;
public class App { public static void main(String[] args) { try (Daytona daytona = new Daytona()) { CreateSecretParams params = new CreateSecretParams(); params.setName("my-secret"); params.setValue("secret-value"); params.setDescription("Optional description"); params.setHosts(List.of("api.example.com"));
Secret secret = daytona.secret().create(params); } }}curl 'https://app.daytona.io/api/secret' \ --request POST \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "name": "my-secret", "value": "secret-value", "description": "Optional description", "hosts": ["api.example.com"]}'Use a secret in a sandbox
Section titled “Use a secret in a sandbox”Daytona provides methods to inject a secret into a sandbox.
- When creating the sandbox, pass
secretsas a map of environment variable name to secret name. - Daytona sets that environment variable to the secret’s placeholder, not to the real value.
- Your code reads the environment variable as usual and includes it in an outbound request.
- The outbound proxy substitutes the real value into requests sent to the secret’s allowed hosts, and leaves the placeholder unchanged for any other host.
Each entry must have exactly one key. The environment variable name (the key) can differ from the secret name (the value), so the same stored secret can be exposed under different variable names in different sandboxes.
from daytona import CreateSandboxFromSnapshotParams, Daytona
daytona = Daytona()sandbox = daytona.create(CreateSandboxFromSnapshotParams( language="python", secrets={ "MY_API_KEY": "my-secret", "MY_DB_PASSWORD": "my-db-secret", },))import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()const sandbox = await daytona.create({ language: 'typescript', secrets: { MY_API_KEY: 'my-secret', MY_DB_PASSWORD: 'my-db-secret', },})require 'daytona'
daytona = Daytona::Daytona.newsandbox = daytona.create(Daytona::CreateSandboxFromSnapshotParams.new( language: Daytona::CodeLanguage::PYTHON, secrets: { 'MY_API_KEY' => 'my-secret', 'MY_DB_PASSWORD' => 'my-db-secret' }))sandbox, err := client.Create(context.Background(), types.SnapshotParams{ SandboxBaseParams: types.SandboxBaseParams{ Language: types.CodeLanguagePython, Secrets: map[string]string{ "MY_API_KEY": "my-secret", "MY_DB_PASSWORD": "my-db-secret", }, },})if err != nil { log.Fatal(err)}import io.daytona.sdk.Daytona;import io.daytona.sdk.Sandbox;import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import java.util.Map;
CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();params.setLanguage("python");params.setSecrets(Map.of( "MY_API_KEY", "my-secret", "MY_DB_PASSWORD", "my-db-secret"));
Sandbox sandbox = daytona.create(params);curl 'https://app.daytona.io/api/sandbox' \ --request POST \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "secrets": [ { "MY_API_KEY": "my-secret" }, { "MY_DB_PASSWORD": "my-db-secret" } ]}'List secrets
Section titled “List secrets”Daytona provides methods to list secrets.
List operations return metadata only. Secret values are never returned after creation.
secrets = daytona.secret.list()const secrets = await daytona.secret.list()secrets = daytona.secret.listsecrets, err := client.Secret.List(context.Background())import io.daytona.sdk.model.Secret;
import java.util.List;
List<Secret> secrets = daytona.secret().list();curl 'https://app.daytona.io/api/secret' \ --header 'Authorization: Bearer YOUR_API_KEY'Get a secret
Section titled “Get a secret”Daytona provides methods to get a single secret by its ID.
Get operations return metadata only. Secret values are never returned after creation.
secret = daytona.secret.get(secret_id)const secret = await daytona.secret.get(secretId)secret = daytona.secret.get(secret_id)secret, err := client.Secret.Get(context.Background(), secretId)Secret secret = daytona.secret().get(secretId);curl 'https://app.daytona.io/api/secret/SECRET_ID' \ --header 'Authorization: Bearer YOUR_API_KEY'Update a secret
Section titled “Update a secret”Daytona provides methods to update a secret’s value, description, or host allowlist.
Updating the value rotates the credential. The placeholder stays the same, so existing sandboxes that reference the secret pick up the new value on subsequent requests without recreation. The change takes effect in all sandboxes that use the secret within 15 seconds. Send only the fields you want to change. Omitting a field leaves it unchanged.
from daytona import UpdateSecretParams
secret = daytona.secret.update(secret_id, UpdateSecretParams( value="new-secret-value", hosts=["api.example.com", "*.example.com"],))const secret = await daytona.secret.update(secretId, { value: 'new-secret-value', hosts: ['api.example.com', '*.example.com'],})secret = daytona.secret.update( secret_id, value: 'new-secret-value', hosts: ['api.example.com', '*.example.com'])newValue := "new-secret-value"secret, err := client.Secret.Update(context.Background(), secretId, &types.UpdateSecretParams{ Value: &newValue, Hosts: []string{"api.example.com", "*.example.com"},})import io.daytona.sdk.model.UpdateSecretParams;
import java.util.List;
UpdateSecretParams params = new UpdateSecretParams();params.setValue("new-secret-value");params.setHosts(List.of("api.example.com", "*.example.com"));
Secret secret = daytona.secret().update(secretId, params);curl 'https://app.daytona.io/api/secret/SECRET_ID' \ --request PATCH \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "value": "new-secret-value", "hosts": ["api.example.com", "*.example.com"]}'Delete a secret
Section titled “Delete a secret”Daytona provides methods to delete a secret.
Deleted secrets cannot be recovered, and sandboxes that reference a deleted secret can no longer resolve its value.
daytona.secret.delete(secret_id)await daytona.secret.delete(secretId)daytona.secret.delete(secret_id)err := client.Secret.Delete(context.Background(), secretId)daytona.secret().delete(secretId);curl 'https://app.daytona.io/api/secret/SECRET_ID' \ --request DELETE \ --header 'Authorization: Bearer YOUR_API_KEY'Permissions
Section titled “Permissions”Secrets are scoped to an organization. Managing them requires the manage:secrets permission, which you can grant to an organization role or an API key. Create, update, and delete operations are recorded in the audit logs. Secret values are masked in audit entries.