Environment variables can be passed when the Durable Object Container API starts a Container, or through the envVars field on the Container class.
Secrets can be passed into a Container by using Worker Secrets or the Secret Store, then passing them into the Container as environment variables.
KV values can be passed into a Container by using Workers KV, then reading the values and passing them into the Container as environment variables.
These examples show the various ways to pass in secrets, KV values, and environment variables. In each, we will be passing in:
- the variable
"ENV_VAR"as a hard-coded environment variable - the secret
"WORKER_SECRET"as a secret from Worker Secrets - the secret
"SECRET_STORE_SECRET"as a secret from the Secret Store - the value
"KV_VALUE"as a value from Workers KV
In practice, you may just use one of the methods for storing secrets and data, but we will show all methods for completeness.
First, let's create the "WORKER_SECRET" secret in Worker Secrets:
npx wrangler secret put WORKER_SECRETyarn wrangler secret put WORKER_SECRETpnpm wrangler secret put WORKER_SECRETThen, let's create a store called "demo" in the Secret Store, and add
the "SECRET_STORE_SECRET" secret to it:
npx wrangler secrets-store store create demo --remoteyarn wrangler secrets-store store create demo --remotepnpm wrangler secrets-store store create demo --remotenpx wrangler secrets-store secret create demo --name SECRET_STORE_SECRET --scopes workers --remoteyarn wrangler secrets-store secret create demo --name SECRET_STORE_SECRET --scopes workers --remotepnpm wrangler secrets-store secret create demo --name SECRET_STORE_SECRET --scopes workers --remoteNext, let's create a KV namespace called DEMO_KV and add a key-value pair:
npx wrangler kv namespace create DEMO_KVyarn wrangler kv namespace create DEMO_KVpnpm wrangler kv namespace create DEMO_KVnpx wrangler kv key put --binding DEMO_KV KV_VALUE 'Hello from KV!'yarn wrangler kv key put --binding DEMO_KV KV_VALUE 'Hello from KV!'pnpm wrangler kv key put --binding DEMO_KV KV_VALUE 'Hello from KV!'For full details on how to create secrets, see the Workers Secrets documentation and the Secret Store documentation. For KV setup, see the Workers KV documentation.
Next, we need to add bindings to access our secrets, KV values, and environment variables in Wrangler configuration.
{
"name": "my-container-worker",
"vars": {
"ENV_VAR": "my-env-var",
"CONTAINER_IMAGE": "registry.cloudflare.com/<ACCOUNT_ID>/my-container:latest"
},
"secrets_store_secrets": [
{
"binding": "SECRET_STORE",
"store_id": "demo",
"secret_name": "SECRET_STORE_SECRET"
}
],
"kv_namespaces": [
{
"binding": "DEMO_KV",
"id": "<your-kv-namespace-id>"
}
]
// rest of the configuration...
}name = "my-container-worker"
[vars]
ENV_VAR = "my-env-var"
CONTAINER_IMAGE = "registry.cloudflare.com/<ACCOUNT_ID>/my-container:latest"
[[secrets_store_secrets]]
binding = "SECRET_STORE"
store_id = "demo"
secret_name = "SECRET_STORE_SECRET"
[[kv_namespaces]]
binding = "DEMO_KV"
id = "<your-kv-namespace-id>"Note that "WORKER_SECRET" does not need to be specified in the Wrangler config file, as it is automatically
added to env.
Also note that we did not configure anything specific for environment variables, secrets, or KV values in the container-related portion of the Wrangler configuration file.
Pass synchronous Worker variables and secrets when the Container starts. When using the raw API with startup options, provide a deployed image reference.
import { DurableObject } from "cloudflare:workers";
export class MyContainer extends DurableObject {
constructor(ctx, env) {
super(ctx, env);
ctx.blockConcurrencyWhile(async () => {
if (!ctx.container.running) {
ctx.container.start({
image: env.CONTAINER_IMAGE,
enableInternet: true,
env: {
ENV_VAR: env.ENV_VAR,
WORKER_SECRET: env.WORKER_SECRET,
},
});
}
});
}
}import { DurableObject } from "cloudflare:workers";
interface Env {
CONTAINER_IMAGE: string;
ENV_VAR: string;
WORKER_SECRET: string;
}
export class MyContainer extends DurableObject<Env> {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
ctx.blockConcurrencyWhile(async () => {
if (!ctx.container!.running) {
ctx.container!.start({
image: env.CONTAINER_IMAGE,
enableInternet: true,
env: {
ENV_VAR: env.ENV_VAR,
WORKER_SECRET: env.WORKER_SECRET,
},
});
}
});
}
}// https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global
import { env } from "cloudflare:workers";
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
sleepAfter = "10s";
envVars = {
WORKER_SECRET: env.WORKER_SECRET,
ENV_VAR: env.ENV_VAR,
// we can't set the secret store binding or KV values as defaults here, as getting their values is asynchronous
};
}Every instance of this Container will now have these variables and secrets
set as environment variables when it launches.
But what if you want to set environment variables on a per-instance basis?
Pass the values when starting each instance. The raw API example defines a launch() RPC method on the Durable Object. The class version uses startAndWaitForPorts().
import { DurableObject } from "cloudflare:workers";
export class MyContainer extends DurableObject {
launch(image, env) {
if (this.ctx.container.running) {
throw new Error("Container is already running");
}
this.ctx.container.start({ image, enableInternet: true, env });
}
}
function required(value, name) {
if (value === null) {
throw new Error(`${name} was not found`);
}
return value;
}
export default {
async fetch(request, env) {
if (new URL(request.url).pathname !== "/launch-instances") {
return new Response("Not found", { status: 404 });
}
const secretStoreSecret = await env.SECRET_STORE.get();
const kvValue = required(await env.DEMO_KV.get("KV_VALUE"), "KV_VALUE");
const instanceConfig = required(
await env.DEMO_KV.get("instance-bar-config"),
"instance-bar-config",
);
await Promise.all([
env.MY_CONTAINER.getByName("foo").launch(env.CONTAINER_IMAGE, {
ENV_VAR: `${env.ENV_VAR}foo`,
WORKER_SECRET: env.WORKER_SECRET,
SECRET_STORE_SECRET: secretStoreSecret,
KV_VALUE: kvValue,
}),
env.MY_CONTAINER.getByName("bar").launch(env.CONTAINER_IMAGE, {
ENV_VAR: `${env.ENV_VAR}bar`,
WORKER_SECRET: env.WORKER_SECRET,
SECRET_STORE_SECRET: secretStoreSecret,
KV_VALUE: kvValue,
INSTANCE_CONFIG: instanceConfig,
}),
]);
return new Response("Container instances launched");
},
};import { DurableObject } from "cloudflare:workers";
interface Env {
CONTAINER_IMAGE: string;
DEMO_KV: KVNamespace;
ENV_VAR: string;
MY_CONTAINER: DurableObjectNamespace<MyContainer>;
SECRET_STORE: SecretsStoreSecret;
WORKER_SECRET: string;
}
export class MyContainer extends DurableObject {
launch(image: string, env: Record<string, string>): void {
if (this.ctx.container!.running) {
throw new Error("Container is already running");
}
this.ctx.container!.start({ image, enableInternet: true, env });
}
}
function required(value: string | null, name: string): string {
if (value === null) {
throw new Error(`${name} was not found`);
}
return value;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (new URL(request.url).pathname !== "/launch-instances") {
return new Response("Not found", { status: 404 });
}
const secretStoreSecret = await env.SECRET_STORE.get();
const kvValue = required(await env.DEMO_KV.get("KV_VALUE"), "KV_VALUE");
const instanceConfig = required(
await env.DEMO_KV.get("instance-bar-config"),
"instance-bar-config",
);
await Promise.all([
env.MY_CONTAINER.getByName("foo").launch(env.CONTAINER_IMAGE, {
ENV_VAR: `${env.ENV_VAR}foo`,
WORKER_SECRET: env.WORKER_SECRET,
SECRET_STORE_SECRET: secretStoreSecret,
KV_VALUE: kvValue,
}),
env.MY_CONTAINER.getByName("bar").launch(env.CONTAINER_IMAGE, {
ENV_VAR: `${env.ENV_VAR}bar`,
WORKER_SECRET: env.WORKER_SECRET,
SECRET_STORE_SECRET: secretStoreSecret,
KV_VALUE: kvValue,
INSTANCE_CONFIG: instanceConfig,
}),
]);
return new Response("Container instances launched");
},
};export class MyContainer extends Container {
defaultPort = 8080;
sleepAfter = "10s";
}
export default {
async fetch(request, env) {
if (new URL(request.url).pathname === "/launch-instances") {
let instanceOne = env.MY_CONTAINER.getByName("foo");
let instanceTwo = env.MY_CONTAINER.getByName("bar");
// Each instance gets a different set of environment variables
await instanceOne.startAndWaitForPorts({
startOptions: {
envVars: {
ENV_VAR: env.ENV_VAR + "foo",
WORKER_SECRET: env.WORKER_SECRET,
SECRET_STORE_SECRET: await env.SECRET_STORE.get(),
KV_VALUE: await env.DEMO_KV.get("KV_VALUE"),
},
},
});
await instanceTwo.startAndWaitForPorts({
startOptions: {
envVars: {
ENV_VAR: env.ENV_VAR + "bar",
WORKER_SECRET: env.WORKER_SECRET,
SECRET_STORE_SECRET: await env.SECRET_STORE.get(),
KV_VALUE: await env.DEMO_KV.get("KV_VALUE"),
// You can also read different KV keys for different instances
INSTANCE_CONFIG: await env.DEMO_KV.get("instance-bar-config"),
},
},
});
return new Response("Container instances launched");
}
// ... etc ...
},
};KV values are particularly useful for configuration data that changes infrequently but needs to be accessible to your containers. Since KV operations are asynchronous, you must read the values at runtime when starting containers.
Here are common patterns for using KV with containers:
export default {
async fetch(request, env) {
if (new URL(request.url).pathname !== "/configure-container") {
return new Response("Not found", { status: 404 });
}
const config = await env.DEMO_KV.get("container-config", "json");
const apiEndpoint = required(
await env.DEMO_KV.get("api-endpoint"),
"api-endpoint",
);
const deploymentEnv = required(
await env.DEMO_KV.get("deployment-env"),
"deployment-env",
);
await env.MY_CONTAINER.getByName("configured").launch(env.CONTAINER_IMAGE, {
CONFIG_JSON: JSON.stringify(config),
API_ENDPOINT: apiEndpoint,
DEPLOYMENT_ENV: deploymentEnv,
});
return new Response("Container configured and launched");
},
};export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (new URL(request.url).pathname !== "/configure-container") {
return new Response("Not found", { status: 404 });
}
const config = await env.DEMO_KV.get("container-config", "json");
const apiEndpoint = required(
await env.DEMO_KV.get("api-endpoint"),
"api-endpoint",
);
const deploymentEnv = required(
await env.DEMO_KV.get("deployment-env"),
"deployment-env",
);
await env.MY_CONTAINER.getByName("configured").launch(
env.CONTAINER_IMAGE,
{
CONFIG_JSON: JSON.stringify(config),
API_ENDPOINT: apiEndpoint,
DEPLOYMENT_ENV: deploymentEnv,
},
);
return new Response("Container configured and launched");
},
};export default {
async fetch(request, env) {
if (new URL(request.url).pathname === "/configure-container") {
// Read configuration from KV
const config = await env.DEMO_KV.get("container-config", "json");
const apiUrl = await env.DEMO_KV.get("api-endpoint");
let container = env.MY_CONTAINER.getByName("configured");
await container.startAndWaitForPorts({
startOptions: {
envVars: {
CONFIG_JSON: JSON.stringify(config),
API_ENDPOINT: apiUrl,
DEPLOYMENT_ENV: await env.DEMO_KV.get("deployment-env"),
},
},
});
return new Response("Container configured and launched");
}
},
};export default {
async fetch(request, env) {
if (new URL(request.url).pathname !== "/launch-with-features") {
return new Response("Not found", { status: 404 });
}
const featureFlags = {
ENABLE_FEATURE_A: required(
await env.DEMO_KV.get("feature-a-enabled"),
"feature-a-enabled",
),
ENABLE_FEATURE_B: required(
await env.DEMO_KV.get("feature-b-enabled"),
"feature-b-enabled",
),
DEBUG_MODE: required(
await env.DEMO_KV.get("debug-enabled"),
"debug-enabled",
),
};
await env.MY_CONTAINER.getByName("features").launch(env.CONTAINER_IMAGE, {
...featureFlags,
CONTAINER_VERSION: "1.2.3",
});
return new Response("Container launched with feature flags");
},
};export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (new URL(request.url).pathname !== "/launch-with-features") {
return new Response("Not found", { status: 404 });
}
const featureFlags = {
ENABLE_FEATURE_A: required(
await env.DEMO_KV.get("feature-a-enabled"),
"feature-a-enabled",
),
ENABLE_FEATURE_B: required(
await env.DEMO_KV.get("feature-b-enabled"),
"feature-b-enabled",
),
DEBUG_MODE: required(
await env.DEMO_KV.get("debug-enabled"),
"debug-enabled",
),
};
await env.MY_CONTAINER.getByName("features").launch(
env.CONTAINER_IMAGE,
{
...featureFlags,
CONTAINER_VERSION: "1.2.3",
},
);
return new Response("Container launched with feature flags");
},
};export default {
async fetch(request, env) {
if (new URL(request.url).pathname === "/launch-with-features") {
// Read feature flags from KV
const featureFlags = {
ENABLE_FEATURE_A: await env.DEMO_KV.get("feature-a-enabled"),
ENABLE_FEATURE_B: await env.DEMO_KV.get("feature-b-enabled"),
DEBUG_MODE: await env.DEMO_KV.get("debug-enabled"),
};
let container = env.MY_CONTAINER.getByName("features");
await container.startAndWaitForPorts({
startOptions: {
envVars: {
...featureFlags,
CONTAINER_VERSION: "1.2.3",
},
},
});
return new Response("Container launched with feature flags");
}
},
};Finally, you can also set build-time environment variables that are only available when building the container image via the image_vars field in the Wrangler configuration.