Scopes
Scopes provide hierarchical organization and isolation for your infrastructure. They enable you to group related resources, manage different environments (dev, staging, prod), and control resource lifecycle at multiple levels.
What is a Scope?
A scope is a container for resources that provides:
Namespacing : Unique resource identification within a scope
State isolation : Each scope maintains its own state
Hierarchical organization : Scopes can contain child scopes
Environment separation : Different stages (dev, prod) are separate scopes
Credential management : Scopes can override provider credentials
Every Alchemy application has at least two scopes: the root scope (app name) and a stage scope (e.g., “dev”, “prod”).
Scope Hierarchy
Alchemy applications have a built-in scope hierarchy:
my-app (root scope)
└── dev (stage scope)
├── api (resource)
├── database (resource)
└── storage (child scope)
└── images (resource)
The fully qualified name (FQN) reflects this hierarchy:
my-app/dev/api
my-app/dev/storage/images
Creating Scopes
Application Scope
Create the root application scope with alchemy():
import { alchemy } from "alchemy" ;
const app = await alchemy ( "my-app" );
// Resources created here are in the "my-app/dev" scope
const worker = await Worker ( "api" , {
entrypoint: "./src/api.ts"
});
await app . finalize ();
The stage scope (“dev”) is automatically created based on the --stage flag or ALCHEMY_STAGE environment variable.
Child Scopes
Create child scopes with alchemy.run():
import { alchemy } from "alchemy" ;
const app = await alchemy ( "my-app" );
await alchemy . run ( "storage" , async ( scope ) => {
// Resources here are in "my-app/dev/storage"
const imageBucket = await R2Bucket ( "images" , {
name: "user-images"
});
const documentBucket = await R2Bucket ( "documents" , {
name: "user-documents"
});
});
await app . finalize ();
Scope Properties
Scopes provide access to configuration and metadata:
const app = await alchemy ( "my-app" , {
stage: "prod" ,
password: process . env . ALCHEMY_PASSWORD
});
await alchemy . run ( "database" , async ( scope ) => {
console . log ( scope . appName ); // "my-app"
console . log ( scope . stage ); // "prod"
console . log ( scope . name ); // "database"
console . log ( scope . scopeName ); // "database"
console . log ( scope . chain ); // ["my-app", "prod", "database"]
console . log ( scope . local ); // false (true if --local flag)
console . log ( scope . watch ); // false (true if --watch flag)
console . log ( scope . phase ); // "up" (or "destroy", "read")
});
Scope Options
Stage Configuration
Specify which environment to deploy to:
# Deploy to production
bun ./alchemy.run.ts --stage prod
# Deploy to development (default)
bun ./alchemy.run.ts --stage dev
Or programmatically:
const app = await alchemy ( "my-app" , {
stage: "prod"
});
Different stages maintain completely separate state. Resources in “dev” are independent from resources in “prod”.
Deployment Phases
Scopes operate in different phases:
Phase Description Trigger "up"Create or update resources Default behavior "destroy"Delete all resources --destroy flag"read"Read-only, no changes --read flag or --app for non-selected apps
const app = await alchemy ( "my-app" );
await alchemy . run ( "api" , async ( scope ) => {
if ( scope . phase === "destroy" ) {
console . log ( "Cleaning up resources..." );
}
// Resources automatically respect the phase
const worker = await Worker ( "api" , {
entrypoint: "./src/api.ts"
});
});
await app . finalize ();
Local Development
Enable local simulation mode:
bun ./alchemy.run.ts --local
const app = await alchemy ( "my-app" , {
local: true
});
await alchemy . run ( "api" , async ( scope ) => {
console . log ( scope . local ); // true
// Resources can provide local alternatives
const worker = await Worker ( "api" , {
entrypoint: "./src/api.ts"
});
// Returns local worker URL instead of deployed URL
});
Local mode allows you to develop and test infrastructure code without deploying to the cloud.
Watch Mode
Automatically redeploy on file changes:
bun --watch ./alchemy.run.ts
Resources can detect watch mode:
await alchemy . run ( "api" , async ( scope ) => {
if ( scope . watch ) {
console . log ( "Watch mode enabled - auto-redeploying on changes" );
}
});
Scope State Management
Scopes can store and retrieve custom data:
await alchemy . run ( "cache" , async ( scope ) => {
// Store data in scope state
await scope . set ( "lastDeployment" , new Date (). toISOString ());
// Retrieve data
const lastDeployment = await scope . get < string >( "lastDeployment" );
console . log ( `Last deployed: ${ lastDeployment } ` );
// Delete data
await scope . delete ( "lastDeployment" );
});
Scope state is separate from resource state and persists across deployments.
Physical Name Generation
Scopes provide utilities for generating deterministic resource names:
await alchemy . run ( "services" , async ( scope ) => {
const name = scope . createPhysicalName ( "api" );
// Returns: "my-app-services-api-dev"
const shortName = scope . createPhysicalName ( "db" , "-" , 20 );
// Returns: "my-app-services-d" (truncated to 20 chars)
});
Format: {appName}-{scope-chain}-{id}-{stage}
Provider Credentials
Scopes can override provider credentials:
declare module "alchemy" {
interface ProviderCredentials {
aws ?: {
accessKeyId : string ;
secretAccessKey : string ;
region : string ;
};
}
}
const app = await alchemy ( "my-app" , {
aws: {
accessKeyId: process . env . AWS_ACCESS_KEY_ID ! ,
secretAccessKey: process . env . AWS_SECRET_ACCESS_KEY ! ,
region: "us-east-1"
}
});
await alchemy . run ( "eu-services" , {
aws: {
... app . providerCredentials . aws ,
region: "eu-west-1" // Override region for this scope
}
}, async ( scope ) => {
// Resources in this scope use eu-west-1
});
Scope Lifecycle
Initialization
Scopes are initialized when created:
const app = await alchemy ( "my-app" );
// State store is initialized
// Stage scope is created
await alchemy . run ( "services" , async ( scope ) => {
// Child scope is initialized
// Resources can be created
});
// Child scope is automatically finalized
await app . finalize ();
// Root and stage scopes are finalized
// Orphaned resources are cleaned up
Finalization
Finalization handles cleanup and orphan detection:
Execute deferred operations
Any operations registered with scope.defer() are executed
Finalize child scopes
All child scopes are finalized recursively
Detect orphaned resources
Resources that exist in state but not in code are identified
Destroy orphans
Orphaned resources are deleted using the configured destroy strategy
Cleanup processes
Any cleanup functions registered with scope.onCleanup() are called
const app = await alchemy ( "my-app" );
// Create resources
const worker = await Worker ( "api" , {
entrypoint: "./src/api.ts"
});
// This is critical - finalizes scopes and cleans up orphans
await app . finalize ();
Always call app.finalize() at the end of your script. Without it, orphaned resources won’t be cleaned up.
Process Management
Scopes can spawn and manage long-running processes:
await alchemy . run ( "dev-server" , async ( scope ) => {
// Spawn a process that's automatically tracked
const url = await scope . spawn ( "vite" , {
command: "vite" ,
args: [ "--port" , "3000" ],
extract : ( line ) => {
const match = line . match ( /Local: \s + ( http: \/\/ [ ^ \s ] + ) / );
return match ?.[ 1 ];
}
});
console . log ( `Dev server running at: ${ url } ` );
});
Processes spawned with scope.spawn() are automatically stopped when the process exits.
Cleanup Handlers
Register cleanup functions to run when the process exits:
await alchemy . run ( "tunnel" , async ( scope ) => {
const tunnel = await startTunnel ();
scope . onCleanup ( async () => {
console . log ( "Closing tunnel..." );
await tunnel . close ();
});
});
Scope Isolation
Each scope maintains isolated state:
const app = await alchemy ( "my-app" );
await alchemy . run ( "api" , async ( apiScope ) => {
await apiScope . set ( "version" , "1.0.0" );
});
await alchemy . run ( "worker" , async ( workerScope ) => {
const version = await workerScope . get ( "version" );
console . log ( version ); // undefined - different scope
});
await app . finalize ();
Error Handling
Scopes track errors and propagate them:
await alchemy . run ( "failing-service" , async ( scope ) => {
try {
const worker = await Worker ( "api" , {
entrypoint: "./invalid-path.ts" // This will fail
});
} catch ( error ) {
scope . fail ();
throw error ;
}
});
// Scope is marked as failed
// Finalization will skip orphan cleanup for failed scopes
Adoption Mode
Adopt existing resources that aren’t yet managed:
bun ./alchemy.run.ts --adopt
const app = await alchemy ( "my-app" , {
adopt: true
});
// Will adopt existing resources instead of erroring on conflicts
const bucket = await R2Bucket ( "existing-bucket" , {
name: "my-existing-bucket"
});
Adoption is useful when migrating existing infrastructure to Alchemy.
Quiet Mode
Suppress creation/update/deletion logs:
bun ./alchemy.run.ts --quiet
const app = await alchemy ( "my-app" , {
quiet: true
});
Best Practices
Use meaningful scope names
Choose names that describe the purpose: "api", "database", "storage"
Organize by lifecycle
Group resources that should be deployed/destroyed together
Leverage scope chain
Access parent scope properties via scope.parent when needed
Always finalize
Call await app.finalize() to ensure proper cleanup
Use stage isolation
Maintain separate stages for dev, staging, and production
Advanced Patterns
Conditional Scopes
Create scopes conditionally:
const app = await alchemy ( "my-app" );
if ( app . stage === "prod" ) {
await alchemy . run ( "monitoring" , async () => {
// Production-only monitoring resources
});
}
await app . finalize ();
Scoped Secrets
Different scopes can use different passwords:
await alchemy . run ( "secure-service" , {
password: process . env . SECURE_PASSWORD
}, async ( scope ) => {
// Secrets in this scope use SECURE_PASSWORD
const apiKey = alchemy . secret ( process . env . API_KEY );
});
Next Steps
Resources Learn about creating and managing resources
State Management Understand how state is stored and managed
Secrets Secure sensitive configuration values
Lifecycle Deep dive into deployment phases