A custom Dagster+ agent that extends the standard agent with a Lambda Run Launcher, enabling job execution as AWS Lambda function invocations instead of spinning up new ECS tasks or containers.
This project provides a complete, deployable custom Dagster+ agent:
- Agent: Runs in ECS (always-on) with custom Lambda run launcher
- Code Locations: Standard Dagster code locations
- Job Execution: Invokes AWS Lambda functions instead of creating containers
┌─────────────────────────────────┐
│ Dagster Cloud (SaaS) │
└──────────────┬──────────────────┘
│ gRPC API
┌──────────────▼──────────────────┐
│ Custom Agent (ECS) │
│ - Base Dagster+ Agent │
│ - Lambda Run Launcher │
└──────────┬─────────────┬────────┘
│ │
┌──────▼──────┐ ┌───▼────────────┐
│ Lambda │ │ Lambda │
│ (sync call) │ │ (async event) │
└─────────────┘ └────────────────┘
Important: Dagster agents can only be configured with a single run launcher. In most production scenarios, you'll want to orchestrate both:
- Lambda functions (using this Lambda run launcher)
- Python code (using standard ECS/Kubernetes run launcher)
Since each agent supports only one run launcher, you'll need to deploy multiple agents and use agent queues to route jobs appropriately.
┌──────────────────────────────────────────────────────────────┐
│ Dagster Cloud (SaaS) │
│ - Receives all job requests │
│ - Routes to appropriate agent based on queue tags │
└────────────────┬────────────────────┬────────────────────────┘
│ │
┌────────▼────────┐ ┌────────▼────────────┐
│ Lambda Agent │ │ ECS Agent │
│ (Queue: lambda) │ │ (Queue: ecs) │
│ ECS Fargate │ │ ECS Fargate │
└────┬──────┬─────┘ └────┬────────────────┘
│ │ │
┌──────▼─┐ ┌─▼──────┐ ┌────▼──────────────┐
│Lambda │ │Lambda │ │ECS Tasks │
│(sync) │ │(async) │ │(Python code) │
└────────┘ └────────┘ └───────────────────┘
Important: Agent queues are configured at the code location level in Dagster Cloud, not per-job. Each code location is assigned to a specific agent queue, and all jobs in that location are routed to the corresponding agent.
Lambda Agent - Handles Lambda function invocations:
# dagster.yaml for Lambda agent
instance:
agent:
queues:
- lambda # This agent processes code locations assigned to "lambda" queue
run_launcher:
module: lambda_run_launcher
class: LambdaRunLauncherECS Agent - Handles Python code execution:
# dagster.yaml for ECS agent
instance:
agent:
queues:
- ecs # This agent processes code locations assigned to "ecs" queue
run_launcher:
module: dagster_aws.ecs
class: EcsRunLauncherYou need to create separate code locations for Lambda and ECS jobs, each specifying its target queue in dagster_cloud.yaml.
my-dagster-repo/
├── lambda_jobs/ # Code location for Lambda agent
│ ├── dagster_cloud.yaml # Specifies queue: lambda
│ ├── __init__.py
│ └── jobs.py # Jobs that invoke Lambda functions
│
└── python_jobs/ # Code location for ECS agent
├── dagster_cloud.yaml # Specifies queue: ecs
├── __init__.py
└── jobs.py # Jobs that run Python code
lambda_jobs/dagster_cloud.yaml:
locations:
- location_name: lambda_jobs
code_source:
package_name: lambda_jobs
agent_queue: lambda # Routes to Lambda agentpython_jobs/dagster_cloud.yaml:
locations:
- location_name: python_jobs
code_source:
package_name: python_jobs
agent_queue: ecs # Routes to ECS agentlambda_jobs/jobs.py (served by Lambda agent):
import dagster as dg
@dg.op(config_schema={"api_endpoint": str})
def call_api(context):
"""Lightweight API call - runs in Lambda."""
pass
@dg.job(
tags={
"lambda/function_name": "my-handler",
"lambda/invocation_type": "Event"
}
)
def api_job():
"""Automatically routed to Lambda agent via agent_queue: lambda."""
call_api()
defs = dg.Definitions(jobs=[api_job])python_jobs/jobs.py (served by ECS agent):
import dagster as dg
import pandas as pd
@dg.op
def process_data(context):
"""Heavy processing - runs in ECS."""
df = pd.DataFrame({"data": range(1000000)})
return df
@dg.job
def processing_job():
"""Automatically routed to ECS agent via agent_queue: ecs."""
process_data()
defs = dg.Definitions(jobs=[processing_job])| Use Lambda Agent | Use ECS Agent |
|---|---|
| Configuration-driven jobs | Python code execution |
| Invoking existing Lambda functions | Custom pip dependencies |
| Fast execution (<15 min) | Long-running jobs (>15 min) |
| Event-driven workflows | Stateful computations |
| Instant invocation (no cold start) | Memory-intensive tasks (>10GB) |
| API integrations | GPU/specialized hardware |
Instead of writing Python code, you can define Lambda-backed assets and jobs purely in YAML using a custom Dagster component:
Create a component YAML file:
# components/my_lambda_asset.yaml
type: components.lambda_component.LambdaFunctionComponent
attributes:
lambda_config:
function_name: my-data-processor
invocation_type: Event
payload_mode: full
asset:
key: processed_data
description: Data processed by Lambda
config_schema:
input_path:
type: string
output_path:
type: string
schedule:
cron_schedule: "0 1 * * *"
execution_timezone: UTCLoad components in your Dagster code:
import dagster as dg
# Automatically loads all component YAML files
defs = dg.Definitions.from_yaml_directory("components")Benefits of YAML Components:
- No Python code required for simple Lambda invocations
- Easy for non-Python users to define jobs
- Configuration lives separate from code
- Great for standardized patterns
See: examples/component_examples/ for complete examples including:
- Simple assets
- Assets with dependencies and schedules
- Partitioned assets (daily, weekly, static)
- Multi-step jobs
- Wrapping existing Lambda functions
# Build Lambda agent
cd lambda_run_launcher/infra
export ECR_REGISTRY=your-registry
export IMAGE_NAME=dagster-lambda-agent
./build-and-push.sh
# Deploy to ECS
aws ecs register-task-definition \
--cli-input-json file://ecs-task-definition-lambda.json
aws ecs create-service \
--cluster your-cluster \
--service-name dagster-lambda-agent \
--task-definition dagster-lambda-agent \
--desired-count 1# Use standard Dagster+ ECS agent
# Configure with ECS run launcher and queue: ecs
# Example ECS agent dagster.yaml
cat > dagster-ecs.yaml << EOF
instance:
class: DagsterCloudAgentInstance
agent:
queues:
- ecs
run_launcher:
module: dagster_aws.ecs
class: EcsRunLauncher
config:
cluster: your-ecs-cluster
subnets:
- subnet-xxxxx
security_group_ids:
- sg-xxxxx
EOF
# Deploy ECS agent
aws ecs create-service \
--cluster your-cluster \
--service-name dagster-ecs-agent \
--task-definition dagster-ecs-agent \
--desired-count 1Create two separate agent configurations:
Lambda Agent (dagster-lambda.yaml):
instance:
class: DagsterCloudAgentInstance
agent:
queues:
- lambda # Only handle jobs tagged with queue: lambda
run_launcher:
module: lambda_run_launcher
class: LambdaRunLauncher
config:
default_function_name: ${DEFAULT_LAMBDA_FUNCTION}
region_name: ${AWS_REGION:us-east-1}
payload_mode: ${PAYLOAD_MODE:full}ECS Agent (dagster-ecs.yaml):
instance:
class: DagsterCloudAgentInstance
agent:
queues:
- ecs # Only handle jobs tagged with queue: ecs
run_launcher:
module: dagster_aws.ecs
class: EcsRunLauncher
config:
cluster: your-ecs-cluster
subnets:
- subnet-xxxxx
security_group_ids:
- sg-xxxxx
task_definition: dagster-run-workerSingle ECS Agent (all jobs):
- Every job waits for ECS task to spin up (~30-60 seconds)
- Resource contention during high-volume periods
- Fixed concurrency limits
Multi-Agent Setup (Lambda + ECS):
- Lambda jobs execute instantly (no task startup wait)
- Unlimited concurrency for lightweight jobs
- ECS only for jobs that actually need it
Key Benefit: Sub-second job startup for Lambda vs 30-60 second ECS task startup
Example Impact: A job that runs every 5 minutes with 10-second execution:
- ECS: 40-second total (30s startup + 10s execution) = 8 jobs/hour max
- Lambda: 10-second total (instant startup + 10s execution) = 360 jobs/hour possible
If you don't configure agent queues:
- All agents will attempt to process all code locations
- First agent to claim the job wins
- Can lead to failures if wrong agent picks up a job
Best Practice: Always configure explicit queues in production:
- Agents: Configure
queues: [lambda]orqueues: [ecs]in agent's dagster.yaml - Code Locations: Add
agent_queue: lambdaoragent_queue: ecsin each location's dagster_cloud.yaml - Result: Deterministic routing, jobs always go to the correct agent
Configuration on BOTH sides is required: The code location declares its requirements (agent_queue), and the agent declares its capabilities (queues).
-
Deploy Two Agents:
- Lambda agent with
queues: [lambda]in dagster.yaml - ECS agent with
queues: [ecs]in dagster.yaml
- Lambda agent with
-
Structure Your Repository:
my-repo/ ├── lambda_jobs/ │ ├── dagster_cloud.yaml # agent_queue: lambda │ └── jobs.py └── python_jobs/ ├── dagster_cloud.yaml # agent_queue: ecs └── jobs.py -
Configure Queue Routing:
- Each code location specifies its target queue in
dagster_cloud.yaml - Add
agent_queue: lambdaoragent_queue: ecsto each location
- Each code location specifies its target queue in
-
Deploy to Dagster Cloud:
- Push your code to Git (or deploy via CLI)
- Dagster Cloud reads
dagster_cloud.yamlfrom each location - Jobs automatically route to correct agent based on
agent_queue
Important: Both the agent (via queues: [...]) and code location (via agent_queue: ...) must specify matching queue names.
In Dagster Cloud UI:
- Go to Deployment Settings > Agents
- You'll see both agents listed with their queues
- Monitor health and job assignment for each agent
- View which agent executed each run in run details
- Each code location shows which queue (and therefore agent) serves it
lambda_run_launcher/
├── Dockerfile # Custom agent image (extends dagster-cloud-agent)
├── dagster.yaml # Agent configuration
├── requirements.txt # Python dependencies
├── README.md # This file
│
├── app/ # Lambda run launcher code
│ ├── __init__.py
│ └── lambda_run_launcher.py # Main launcher implementation
│
├── infra/ # Deployment infrastructure
│ ├── build-and-push.sh # Docker build and push script
│ ├── ecs-task-definition.json # ECS task definition template
│ ├── iam-policy.json # IAM policy for agent role
│ └── env.example # Environment variables template
│
└── examples/ # Usage examples
├── dagster_job_example.py # Dagster job examples
└── lambda_handler.py # Example Lambda handler
- AWS Account with ECR, ECS, and Lambda access
- Dagster Cloud organization and deployment
- Docker installed locally
- AWS CLI configured
# Set your ECR registry
export ECR_REGISTRY=123456789012.dkr.ecr.us-east-1.amazonaws.com
# Build and push the agent image
cd infra
chmod +x build-and-push.sh
./build-and-push.shCreate an IAM role for the ECS task with Lambda invocation permissions:
# Create the role (if it doesn't exist)
aws iam create-role \
--role-name DagsterLambdaAgentRole \
--assume-role-policy-document file://trust-policy.json
# Attach the Lambda invocation policy
aws iam put-role-policy \
--role-name DagsterLambdaAgentRole \
--policy-name LambdaInvocationPolicy \
--policy-document file://iam-policy.json# Store agent token in Secrets Manager
aws secretsmanager create-secret \
--name dagster/agent-token \
--secret-string "your-agent-token-from-dagster-cloud"# Register task definition
aws ecs register-task-definition \
--cli-input-json file://ecs-task-definition.json
# Create or update ECS service
aws ecs create-service \
--cluster your-cluster-name \
--service-name dagster-lambda-agent \
--task-definition dagster-lambda-agent \
--desired-count 1 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-xxx],securityGroups=[sg-xxx],assignPublicIp=ENABLED}"# Create deployment package
cd examples
zip lambda_handler.zip lambda_handler.py
# Deploy Lambda function
aws lambda create-function \
--function-name dagster-example-handler \
--runtime python3.11 \
--handler lambda_handler.lambda_handler \
--role arn:aws:iam::123456789012:role/lambda-execution-role \
--zip-file fileb://lambda_handler.zipimport dagster as dg
@dg.op
def my_op(context):
context.log.info("Running in Lambda!")
return {"status": "success"}
@dg.job(
tags={
"lambda/function_name": "dagster-example-handler",
"lambda/invocation_type": "Event"
}
)
def my_lambda_job():
my_op()Deploy this code to your Dagster Cloud code location and run the job!
The agent configuration supports environment variable expansion using ${VAR_NAME} or ${VAR_NAME:default} syntax:
instance:
class: DagsterCloudAgentInstance
run_launcher:
module: lambda_run_launcher
class: LambdaRunLauncher
config:
default_function_name: ${DEFAULT_LAMBDA_FUNCTION:}
default_invocation_type: ${DEFAULT_INVOCATION_TYPE:Event}
region_name: ${AWS_REGION:us-east-1}
env_vars:
- DAGSTER_CLOUD_ORG_ID
- DAGSTER_CLOUD_DEPLOYMENT_ID
sync_timeout: ${LAMBDA_SYNC_TIMEOUT:300}Required:
DAGSTER_CLOUD_AGENT_TOKEN- Agent authentication token from Dagster Cloud
Recommended:
AWS_REGION- AWS region for Lambda functionsDEFAULT_LAMBDA_FUNCTION- Default Lambda function nameDEFAULT_INVOCATION_TYPE- Default invocation type (Event or RequestResponse)
See infra/env.example for complete list.
Asynchronous (Event) - Default
@dg.job(tags={"lambda/invocation_type": "Event"})
def async_job():
my_op()- Fire-and-forget execution
- No timeout limits (besides Lambda's 15-minute max)
- Suitable for long-running or background jobs
Synchronous (RequestResponse)
@dg.job(tags={"lambda/invocation_type": "RequestResponse"})
def sync_job():
my_op()- Agent waits for Lambda completion
- Response available in engine events
- Limited to 15-minute Lambda max execution time
Per-Job Tags (Recommended)
@dg.job(tags={"lambda/function_name": "my-lambda"})
def job1():
passUsing ARN
@dg.job(tags={"lambda/function_arn": "arn:aws:lambda:..."})
def job2():
passRun Configuration
@dg.job(config={
"execution": {
"config": {
"lambda_function": "my-lambda",
"invocation_type": "Event"
}
}
})
def job3():
passGlobal Default
Set DEFAULT_LAMBDA_FUNCTION environment variable in ECS task.
Your Lambda function receives a comprehensive payload:
{
"dagster_run": {
"run_id": "uuid",
"job_name": "my_job",
"deployment_name": "prod",
"location_name": "my_location",
"agent_id": "agent-id",
"tags": {...}
},
"run_config": {
"ops": {
"my_op": {
"config": {...}
}
}
},
"environment_variables": {
"DAGSTER_CLOUD_ORG_ID": "...",
"AWS_REGION": "..."
},
"dagster_cloud": {
"org_id": "...",
"deployment_id": "..."
},
"metadata": {
"launched_at": "2026-01-09T12:00:00Z",
"launcher_version": "1.0.0",
"region": "us-east-1"
}
}If you have existing Lambda functions that expect a different payload format, you don't need to modify them! The launcher supports multiple payload modes to accommodate existing Lambda functions.
Sends the comprehensive payload structure shown above. Best for new Lambda functions built specifically for Dagster.
# dagster.yaml
run_launcher:
config:
payload_mode: 'full' # Default, can be omittedSends only the run_config dictionary. Useful if your existing Lambda expects Dagster's config structure.
# dagster.yaml
run_launcher:
config:
payload_mode: 'config_only'Your Lambda receives:
{
"ops": {
"my_op": {
"config": {
"param1": "value1"
}
}
}
}Sends only the ops configuration from run_config.ops. Perfect for Lambda functions that process op configs.
# dagster.yaml
run_launcher:
config:
payload_mode: 'ops_only'Your Lambda receives:
{
"my_op": {
"config": {
"param1": "value1"
}
}
}Extracts a specific path from run_config using dot notation. Ideal for Lambda functions expecting a specific config structure.
# dagster.yaml
run_launcher:
config:
payload_mode: 'custom'
payload_config_path: 'ops.my_op.config'Your Lambda receives exactly the extracted value:
{
"param1": "value1",
"param2": "value2"
}Phase 1: No Lambda Changes (Start here for existing functions)
payload_mode: 'custom'
payload_config_path: 'ops.my_op.config'Your existing Lambda continues to work without any code changes!
Phase 2: Optional Enhancement
payload_mode: 'config_only'Update Lambda to handle multiple ops if needed.
Phase 3: Full Integration (For new Lambda functions)
payload_mode: 'full'Access rich metadata, environment variables, and Dagster Cloud info.
Suppose you have an existing Lambda that expects:
{
"source_bucket": "my-bucket",
"destination_bucket": "other-bucket",
"file_pattern": "*.csv"
}Dagster job with per-job payload configuration:
@dg.op(config_schema={
"source_bucket": str,
"destination_bucket": str,
"file_pattern": str
})
def copy_files(context):
pass
@dg.job(tags={
"lambda/function_name": "existing-s3-copy",
"lambda/payload_mode": "custom",
"lambda/payload_config_path": "ops.copy_files.config"
})
def copy_job():
copy_files()This extracts just the op config and sends it to Lambda. No changes to agent configuration needed.
No Lambda code changes needed! The launcher extracts and sends exactly what your Lambda expects.
See examples/existing_lambda_examples.py and examples/existing_lambda_handler.py for more detailed examples.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction",
"lambda:InvokeAsync"
],
"Resource": "arn:aws:lambda:*:*:function:dagster-*"
}
]
}Standard Lambda execution role plus any additional permissions your functions need (S3, DynamoDB, etc.)
See the examples/ directory for:
dagster_job_example.py- Various job patterns using Lambda launcher (new Lambda functions)lambda_handler.py- Example Lambda function handler (new Lambda functions)existing_lambda_examples.py- How to use existing Lambda functions with different payload modesexisting_lambda_handler.py- Example existing Lambda handlers and migration patternsmulti_agent_example.py- Multi-agent setup with Lambda + ECS agents, queue routing, and job examples
dagster_cloud_lambda.yaml- Example dagster_cloud.yaml for Lambda code location (agent_queue: lambda)dagster_cloud_ecs.yaml- Example dagster_cloud.yaml for ECS code location (agent_queue: ecs)
component_examples/- Dagster components for defining Lambda assets and jobs in pure YAMLlambda_component.py- Custom Lambda Function Componentlambda_asset_simple.yaml- Basic Lambda-backed assetlambda_asset_with_deps.yaml- Asset with dependencies and schedulelambda_asset_partitioned.yaml- Daily partitioned assetlambda_existing_function.yaml- Wrapping existing Lambda with custom payloadlambda_job_multi_step.yaml- Multi-step ETL joblambda_static_partitions.yaml- Asset with static partitionsdagster_project_example/- Complete example project using components
-
Build image locally:
docker build -t dagster-lambda-agent:dev . -
Run locally (testing):
docker run -e DAGSTER_CLOUD_AGENT_TOKEN=your-token \ -e AWS_REGION=us-east-1 \ -e AWS_ACCESS_KEY_ID=xxx \ -e AWS_SECRET_ACCESS_KEY=xxx \ dagster-lambda-agent:dev
-
Use ECR for image storage
cd infra ECR_REGISTRY=your-registry.amazonaws.com ./build-and-push.sh -
Use Secrets Manager for sensitive data
- Store agent token in Secrets Manager
- Reference in ECS task definition
-
Use IAM roles instead of access keys
- Assign IAM role to ECS task
- No need for AWS credentials in environment
-
Enable CloudWatch logging
- Configure in ECS task definition
- Monitor agent logs in CloudWatch
-
Use ECS Service for high availability
- Set desired count to 1 (agent should run as singleton)
- Enable ECS Service auto-recovery
View agent logs in CloudWatch Logs:
aws logs tail /ecs/dagster-lambda-agent --followCheck Lambda logs for job execution:
aws logs tail /aws/lambda/dagster-example-handler --follow- View run logs in Dagster Cloud UI
- Check engine events for Lambda invocation details
- Monitor run status and results
Check:
- Agent token is valid and stored in Secrets Manager
- ECS task role has permissions to read secrets
- Network configuration allows outbound HTTPS to Dagster Cloud
Logs:
aws logs tail /ecs/dagster-lambda-agent --followCheck:
- Lambda function name/ARN is correct
- IAM role has
lambda:InvokeFunctionpermission - Lambda function exists in the specified region
Verify permissions:
aws lambda get-function --function-name dagster-example-handlerError: Payload exceeds Lambda limit (256KB)
Solutions:
- Reduce run_config size
- Store large data in S3 and pass references
- Use environment variables instead of config where possible
Error: Lambda function 'xxx' not found
Check:
- Function name is spelled correctly
- Function exists in the same region as configured
- Agent has permission to invoke the function
- Execution Time: Lambda has 15-minute maximum timeout
- Payload Size: 256KB limit on request payload
- No Termination: Cannot stop Lambda functions once invoked
- No Code Execution: Lambda functions must be pre-deployed
- Async Status: No built-in status tracking for async invocations
Instant Execution: Lambda functions are already running - no container startup time
- Lambda: <1 second to start execution
- ECS Task: 30-60 seconds to spin up container
High Concurrency: Lambda scales automatically
- Lambda: Thousands of concurrent executions
- ECS Task: Limited by cluster capacity
Event-Driven: Perfect for reactive workflows
- Trigger on S3 events, API calls, schedule events
- No waiting for containers to be ready
Scenario: API webhook that triggers data validation (5-second execution)
| Approach | Startup Time | Execution Time | Total Time | Max Throughput |
|---|---|---|---|---|
| ECS Task | 30-60s | 5s | 35-65s | ~60 jobs/hour |
| Lambda | <1s | 5s | ~6s | Thousands/hour |
Result: Lambda is 6-10x faster for lightweight jobs
Use Lambda Agent for:
- Webhooks: Respond to external events instantly
- Data validation: Quick checks on incoming data
- Notifications: Send alerts without delay
- Triggers: Start longer processes quickly
- API integrations: Call external services
- Status checks: Poll for changes frequently
Use ECS Agent for:
- Data processing: Transform large datasets with pandas/spark
- ML training: Long-running model training
- Batch jobs: Process thousands of records
- Complex pipelines: Multi-step Python workflows
Add custom variables to pass to Lambda:
-
Add to
env_varsin dagster.yaml:env_vars: - CUSTOM_API_KEY - DATABASE_URL
-
Set in ECS task definition:
"environment": [ {"name": "CUSTOM_API_KEY", "value": "xxx"} ]
Use different Lambda functions for different job types:
@dg.job(tags={"lambda/function_name": "etl-handler"})
def etl_job():
pass
@dg.job(tags={"lambda/function_name": "reporting-handler"})
def reporting_job():
passImplement routing in your Lambda handler:
def lambda_handler(event, context):
job_name = event['dagster_run']['job_name']
if job_name == 'etl_job':
return handle_etl(event)
elif job_name == 'reporting_job':
return handle_reporting(event)For async invocations, implement callbacks:
# In Lambda
def report_status_to_dagster(run_id, status):
# Call Dagster Cloud API to update run status
passTo upgrade the agent:
-
Pull latest Dagster Cloud agent image:
docker pull dagster/dagster-cloud-agent:latest
-
Rebuild custom image:
cd infra ./build-and-push.sh -
Update ECS service with new image:
aws ecs update-service \ --cluster your-cluster \ --service dagster-lambda-agent \ --force-new-deployment
Contributions welcome! Please:
- Follow existing code style
- Add tests for new features
- Update documentation
- Test with both sync and async invocations
- Dagster Docs: https://docs.dagster.io
- AWS Lambda Docs: https://docs.aws.amazon.com/lambda/
- AWS ECS Docs: https://docs.aws.amazon.com/ecs/
- Project Issues: GitHub Issues
- ACA Agent - Azure Container Apps example
- Dagster AWS - Official AWS integration
This project follows Dagster's licensing.
- Initial release
- Custom agent extending Dagster+ base agent
- Lambda Run Launcher with sync/async support
- Complete ECS deployment setup
- Example Lambda handlers and Dagster jobs
- Comprehensive documentation
# Build Lambda agent image
cd infra && ./build-and-push.sh
# Deploy Lambda agent
aws ecs update-service --cluster xxx --service dagster-lambda-agent --force-new-deploymentCreate dagster_cloud.yaml in each code location:
lambda_jobs/dagster_cloud.yaml:
locations:
- location_name: lambda_jobs
code_source:
package_name: lambda_jobs
agent_queue: lambda # Routes to Lambda agentpython_jobs/dagster_cloud.yaml:
locations:
- location_name: python_jobs
code_source:
package_name: python_jobs
agent_queue: ecs # Routes to ECS agentLambda Job Example (in lambda_jobs code location):
@dg.job(tags={
"lambda/function_name": "my-handler"
})
def lambda_job():
my_op()ECS Job Example (in python_jobs code location):
@dg.job
def python_job():
my_python_op()Note: Queue routing is determined by agent_queue in dagster_cloud.yaml, not job tags.
Lambda Agent (dagster.yaml):
instance:
agent:
queues: [lambda]
run_launcher:
module: lambda_run_launcher
class: LambdaRunLauncherECS Agent (separate dagster.yaml):
instance:
agent:
queues: [ecs]
run_launcher:
module: dagster_aws.ecs
class: EcsRunLauncherConfigure payload mode at the agent level (applies to all jobs) or per-job via tags.
Agent-level configuration (dagster.yaml):
# Default for all jobs using this agent
payload_mode: 'full' # or 'config_only', 'ops_only', 'custom'Per-job configuration (recommended for multiple Lambda functions):
# Job 1: New Lambda expecting full Dagster payload
@dg.job(tags={
"lambda/function_name": "new-lambda",
"lambda/payload_mode": "full" # Optional - full is default
})
def job1(): ...
# Job 2: Existing Lambda expecting just run_config
@dg.job(tags={
"lambda/function_name": "existing-lambda-1",
"lambda/payload_mode": "config_only"
})
def job2(): ...
# Job 3: Existing Lambda expecting custom format
@dg.job(tags={
"lambda/function_name": "existing-lambda-2",
"lambda/payload_mode": "custom",
"lambda/payload_config_path": "ops.my_op.config"
})
def job3(): ...Available modes:
full: Complete payload with run metadata, config, env vars (default)config_only: Just the run_config dictionaryops_only: Just the ops config from run_config.opscustom: Extract specific path using dot notation (requirespayload_config_path)
# Lambda agent logs
aws logs tail /ecs/dagster-lambda-agent --follow
# ECS agent logs
aws logs tail /ecs/dagster-ecs-agent --follow
# Lambda function logs
aws logs tail /aws/lambda/my-handler --follow# Check agent status in Dagster Cloud
# Go to: Deployment Settings > Agents
# Test Lambda invocation
aws lambda invoke --function-name my-handler response.json
# Monitor ECS services
aws ecs describe-services --cluster xxx --services dagster-lambda-agent dagster-ecs-agent