0% complete
Infrastructure Track Medium-Hard 1-2 days

Browserbox-Style Container Runner

Build infrastructure for on-demand browser containers. Learn Step Functions, ECS, and serverless orchestration.

🎯 The Mission

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.

🏖️ Sandbox Rules

Architecture

📊 BROWSER RUNNER WORKFLOW
┌─────────────────────────────────────────────────────────────────┐
│  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      │
└───────────────────────────┘
            

What You'll Learn

Implementation Guide

Step 1: Create the Screenshot Container

# 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);

Step 2: Create ECS Task Definition

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"
        }
      }
    }
  ])
}

Step 3: Create Step Functions State Machine

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
      }
    }
  })
}

Step 4: Create S3 Bucket with Lifecycle

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
    }
  }
}

Step 5: Test the Workflow

# 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

✓ Success Criteria

📋 Progress Checklist

Stretch Goals