Build infrastructure for on-demand browser containers. Learn Step Functions, ECS, and serverless orchestration.
Tenzai's agent needs to interact with web applications through a real browser. We spin up isolated browser containers on-demand, run Playwright scripts, capture screenshots, and tear down.
Build a simplified version: trigger a workflow, spin up a container, take a screenshot, save to S3, terminate. This is real-world serverless orchestration.
tenzai-sandboxProject=onboarding, Owner=your-name
┌─────────────────────────────────────────────────────────────────┐
│ Trigger (API Gateway or CLI) │
│ POST /screenshot { "url": "https://example.com" } │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step Functions State Machine │
│ 1. RunTask (ECS Fargate) │
│ 2. Wait for task completion │
│ 3. Get results from S3 │
│ 4. Return presigned URL │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────┴──────────┐
│ │
▼ │
┌───────────────────────────┐ │
│ ECS Fargate Task │ │
│ - Playwright container │ │
│ - Navigate to URL │ │
│ - Take screenshot │ │
│ - Upload to S3 │ │
│ - Exit │ │
└─────────────┬─────────────┘ │
│ │
▼ │
┌───────────────────────────┐ │
│ S3 Bucket │ │
│ screenshots/{id}.png │◄────────────┘
│ - Lifecycle: 7 days │
└───────────────────────────┘
# Dockerfile
FROM mcr.microsoft.com/playwright:v1.40.0-focal
WORKDIR /app
COPY screenshot.js .
# Expects URL and S3_KEY as environment variables
CMD ["node", "screenshot.js"]
// screenshot.js
const { chromium } = require('playwright');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const fs = require('fs');
async function main() {
const url = process.env.TARGET_URL;
const s3Key = process.env.S3_KEY;
const bucket = process.env.S3_BUCKET;
console.log(`Taking screenshot of ${url}`);
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle' });
const screenshot = await page.screenshot({ fullPage: true });
await browser.close();
// Upload to S3
const s3 = new S3Client({});
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: s3Key,
Body: screenshot,
ContentType: 'image/png'
}));
console.log(`Uploaded to s3://${bucket}/${s3Key}`);
}
main().catch(console.error);
resource "aws_ecs_task_definition" "screenshot" {
family = "${var.name}-screenshot"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "512"
memory = "1024"
execution_role_arn = aws_iam_role.ecs_execution.arn
task_role_arn = aws_iam_role.ecs_task.arn
container_definitions = jsonencode([
{
name = "screenshot"
image = "${aws_ecr_repository.screenshot.repository_url}:latest"
environment = [
{ name = "S3_BUCKET", value = aws_s3_bucket.screenshots.id }
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.screenshot.name
"awslogs-region" = var.region
"awslogs-stream-prefix" = "ecs"
}
}
}
])
}
resource "aws_sfn_state_machine" "screenshot" {
name = "${var.name}-screenshot-workflow"
role_arn = aws_iam_role.sfn.arn
definition = jsonencode({
Comment = "Take screenshot of a URL"
StartAt = "RunScreenshotTask"
States = {
RunScreenshotTask = {
Type = "Task"
Resource = "arn:aws:states:::ecs:runTask.sync"
Parameters = {
Cluster = aws_ecs_cluster.main.arn
TaskDefinition = aws_ecs_task_definition.screenshot.arn
LaunchType = "FARGATE"
NetworkConfiguration = {
AwsvpcConfiguration = {
Subnets = data.aws_subnets.default.ids
AssignPublicIp = "ENABLED"
}
}
Overrides = {
ContainerOverrides = [{
Name = "screenshot"
Environment = [
{ "Name" = "TARGET_URL", "Value.$" = "$.url" },
{ "Name" = "S3_KEY", "Value.$" = "$.s3_key" }
]
}]
}
}
Next = "GeneratePresignedUrl"
}
GeneratePresignedUrl = {
Type = "Task"
Resource = aws_lambda_function.presign.arn
End = true
}
}
})
}
resource "aws_s3_bucket" "screenshots" {
bucket = "${var.name}-screenshots"
}
resource "aws_s3_bucket_lifecycle_configuration" "screenshots" {
bucket = aws_s3_bucket.screenshots.id
rule {
id = "expire-old-screenshots"
status = "Enabled"
expiration {
days = 7
}
}
}
# Start execution
aws stepfunctions start-execution \
--state-machine-arn arn:aws:states:us-east-1:123456:stateMachine:your-name-screenshot-workflow \
--input '{"url": "https://example.com", "s3_key": "screenshots/test-001.png"}'
# Check execution status
aws stepfunctions describe-execution \
--execution-arn arn:aws:states:us-east-1:123456:execution:...
# View the screenshot
aws s3 presign s3://your-name-screenshots/screenshots/test-001.png