Environments & secrets
An environment is what a sandbox starts with: environment variables, secret files, and which of your account’s credentials it gets — a GitHub token for your repositories, a Boat key of its own, the AI keys of a credential profile. Your account has a default environment, base, which sandboxes get unless they name another.
Every change makes a new version
Section titled “Every change makes a new version”Every change to an environment makes a new version. A sandbox keeps the version it started on, so a change reaches new sandboxes, not running ones; the dashboard upgrades running ones.
Declare an environment with variables and secret files
Section titled “Declare an environment with variables and secret files”import * as Boat from "@alchemy.run/boat";import * as Config from "effect/Config";
export const MyEnv = Boat.Environment("MyEnv", { env: { NODE_ENV: "production", TOKEN: Config.Redacted("AGENT_TOKEN") }, secretFiles: [{ path: ".npmrc", contents: Config.Redacted("NPMRC") }],});Secret file paths are under the home directory: .npmrc, ~/.npmrc and /home/user/.npmrc are the same file. Values stay Redacted in state. Boat stores variables as a .env file, which can’t hold a value with a newline, or with both ' and "; such a value fails with InvalidEnvironmentValue.
An unchanged deploy makes no new version. A changed one updates only what differs.
Start sandboxes on an environment
Section titled “Start sandboxes on an environment”Pass the environment to a declared sandbox:
export const Box = Boat.Sandbox("Box", { environment: MyEnv });A sandbox your code creates names it:
const env = yield* MyEnv;const sandbox = yield* sandboxes.create({ environment: env.name, autoStop: "10 minutes" });Choose which credentials it passes
Section titled “Choose which credentials it passes”An environment you declare passes its own env and secretFiles, and nothing of your account. Each of your credentials is a flag you turn on:
export const Mine = Boat.Environment("Mine", { passGithub: true, // your repositories, and a GitHub token passSandboxCredentials: true, // a Boat key, to run `boat` inside the sandbox});Boat’s own default is to pass everything, which is what base does. Alchemy sets each flag explicitly, so a sandbox holds only what its environment asks for.
AI keys come from a credential profile. Name one, and its keys reach the environment’s sandboxes:
export const WithAi = Boat.Environment("WithAi", { credentialProfile: MyProfile });Give sandboxes to your users
Section titled “Give sandboxes to your users”safeForThirdParties passes nothing of your account, whatever the flags say. Use it for sandboxes someone else gets a shell in:
export const Tenants = Boat.Environment("Tenants", { safeForThirdParties: true });Such sandboxes can still call AI providers through Boat’s proxy, holding placeholders instead of keys. See Keep real keys out of the sandbox.
A single sandbox with none of the account’s secrets doesn’t need an environment: pass noEnv: true on it.
Use an environment the Stack doesn’t manage
Section titled “Use an environment the Stack doesn’t manage”Boat.Environment.lookup finds an environment by name, like the base one every account has. The Stack reads it and never changes or deletes it:
export const Box = Boat.Sandbox("Box", { environment: Boat.Environment.lookup({ name: "base" }),});Pull values from a secrets manager
Section titled “Pull values from a secrets manager”.env is only the default. A Stack’s secrets option reads the same Config values from a secrets manager instead, like Doppler or Infisical, so production keys never sit in a file on anyone’s machine. Pick the source per stage:
import * as Boat from "@alchemy.run/boat";import * as Cloudflare from "@alchemy.run/cloudflare";import * as Doppler from "@alchemy.run/doppler";import * as Alchemy from "alchemy";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import { MyEnv } from "./src/MyEnv.ts";
export default Alchemy.Stack( "MyApp", { providers: Layer.mergeAll(Cloudflare.providers(), Boat.providers()), state: Cloudflare.state(), secrets: ({ stage }) => stage === "prod" ? Doppler.Secrets({ project: "my-app", config: "prd" }) : Alchemy.Secrets.DotEnv(), }, Effect.gen(function* () { const env = yield* MyEnv; return { environment: env.name }; }),);Config.Redacted("AGENT_TOKEN") doesn’t change. See Secret providers.
To call a server running in a sandbox, see Ports & URLs.