Skip to main content

Overview

The ECS Cluster resource provides a managed container orchestration platform for deploying and scaling Docker containers on AWS. Fjall offers three deployment options through the ComputeFactory pattern: Fargate (serverless), EC2 Free Tier (development), and EC2 Spot (cost-optimized). Fjall’s ECS implementation is ideal for:
  • Microservices architectures - Deploy multiple independent services with isolated scaling
  • Containerized web applications - Run web apps with automatic load balancing and SSL
  • API services - Build scalable REST or GraphQL APIs with health checking
  • Background job processing - Execute long-running tasks with automatic recovery
Key features:
  • Three deployment modes - Fargate (serverless), EC2 Spot (cost-optimized), EC2 Free Tier (development)
  • Container Insights V2 - Enhanced monitoring with CloudWatch integration
  • Factory pattern - Unified interface across all compute types
  • Secrets management - Integrated AWS Secrets Manager with KMS encryption
  • Circuit breaker deployment - Automatic rollback on failed deployments
  • ECS Exec support - SSH-like access to running containers for debugging

Resource Class

import { ComputeFactory } from "@fjall/components-infrastructure";
IMPORTANT: The ComputeFactory is the ONLY exported class for creating ECS clusters. Direct imports of FargateCluster, EcsFreeTier, or EcsSpot classes are NOT available from the main package exports.

Basic Usage

Fargate Cluster (Default)

import { ComputeFactory } from "@fjall/components-infrastructure";
import * as ecr from "aws-cdk-lib/aws-ecr";

// Create the compute factory
const computeFactory = ComputeFactory.build("ApiCompute", {
  type: "ecs",           // default
  ecsType: "fargate",    // default
  containerPort: 8080,
  ecrRepository: ecr.Repository.fromRepositoryName(this, "Repo", "my-api-repo")
});

// Deploy in your stack
const cluster = computeFactory(app, this);

With Custom Domain and HTTPS

const computeFactory = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo,
  domainConfig: {
    domainName: "api.example.com"
    // Certificate and hosted zone auto-created
  }
});

const cluster = computeFactory(app, this);

ComputeFactory Configuration

The ComputeFactory.build() method accepts an IComputeProps object with the following properties:

Core Properties

PropertyTypeDescriptionDefault
type"ecs" | "ec2" | "lambda"Compute platform type"ecs"
ecsType"fargate" | "freetier" | "spot"ECS deployment mode (only when type is “ecs”)"fargate"
vpcIVpcVirtual Private Cloud for clusterApp default VPC
ecrRepositoryRepository | RepositoryImageECR repository containing container imageApp default registry

Container Configuration

PropertyTypeDescriptionDefault
containerPortnumberPort the container listens on80
containerEnvironment{[key: string]: string}Environment variables for container{}
containerSecretsImport{[key: string]: SecretImport}Import secrets from other resources/stacks{}
dockerfilePathstringPath to Dockerfile (CLI metadata only, not used in CDK)None

Domain and SSL Configuration

PropertyTypeDescriptionDefault
domainConfigDomainConfigCustom domain with HTTPSNone
When domainConfig is provided:
  • Protocol is automatically set to HTTPS
  • Certificate is auto-created with DNS validation
  • Route 53 A record is automatically configured
DomainConfig type:
type DomainConfig = {
  domainName: string;              // Custom domain (e.g., "api.example.com")
  hostedZone?: FjallHostedZone;    // Route 53 hosted zone (optional)
  certificate?: Certificate;        // ACM certificate (optional)
}

Integration Properties

PropertyTypeDescriptionDefault
connectionsIConnectable[]Resources to allow network connections to[]

Deployment Modes

Fargate (Serverless)

Fargate provides serverless container compute with automatic infrastructure management:
const fargateCompute = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo,
  containerEnvironment: {
    NODE_ENV: "production",
    LOG_LEVEL: "info"
  }
});
Fargate Specifications:
  • Task count: Fixed at 2 tasks (hardcoded in line 340 of ecs.ts)
  • CPU: 256 units (default) - configurable via FargateClusterProps when using direct instantiation
  • Memory: 512 MiB (default) - configurable via FargateClusterProps when using direct instantiation
  • Capacity provider: FARGATE
  • Deployment: Blue/green with circuit breaker
  • Container Insights: V2 enabled
  • ECS Exec: Enabled for debugging
Important Limitations:
  • The desiredCount property exists in the interface but is IGNORED - the value is hardcoded to 2 in the addFargateService method
  • Auto-scaling policy method exists (addScalingPolicy) but is NEVER CALLED in the constructor - auto-scaling is NOT automatically configured
  • To enable auto-scaling, you must manually call the addScalingPolicy method after instantiation

Free Tier (Development)

Optimized for AWS Free Tier eligibility using a single t3.micro instance:
const devCompute = ComputeFactory.build("DevCompute", {
  type: "ecs",
  ecsType: "freetier",
  containerPort: 3000,
  ecrRepository: repo
});
Free Tier Specifications (from ecsFreeTier.ts):
  • Instance type: t3.micro (eligible for 750 hours/month free tier)
  • Architecture: x86 (standard)
  • Min/Max capacity: 1/1 (single instance only)
  • Desired count: 1 task
  • Memory: 400 MiB (line 289)
  • CPU: Not explicitly set at task level
  • Entry point: ["node", "main.js"] (hardcoded, line 294)
  • Container Insights: V2 enabled
  • ECS Exec: Enabled
Cost: Eligible for AWS Free Tier - 750 hours/month of t3.micro at no charge for the first 12 months. When to use: Development and testing environments where cost is a concern and high availability is not required.

Spot Instances (Cost-Optimized)

Uses EC2 Spot Instances with ARM architecture for up to 70% cost savings:
const spotCompute = ComputeFactory.build("SpotCompute", {
  type: "ecs",
  ecsType: "spot",
  containerPort: 8080,
  ecrRepository: repo
});
Spot Instance Specifications (from ecsSpot.ts):
  • Instance type: m8g.large (ARM64 Graviton processor, line 188)
  • Architecture: ARM (AmiHardwareType.ARM, line 191)
  • Min/Max capacity: 2/3 instances (lines 186-187)
  • Desired count: 3 tasks (line 330)
  • Memory: 1024 MiB (line 298)
  • CPU: 1 unit (line 297)
  • Entry point: ["node", "main.js"] (hardcoded, line 303)
  • Capacity rebalancing: Enabled (automatic replacement before interruption)
  • Monitoring: Basic CloudWatch monitoring
  • Container Insights: V2 enabled
Cost: Approximately 70% less than equivalent Fargate deployment, with automatic capacity rebalancing to handle Spot interruptions. When to use: Fault-tolerant workloads that can handle interruptions, such as batch processing, data analysis, or stateless web services.

Factory Pattern Architecture

The ComputeFactory provides a unified interface across all compute types:
// Step 1: Build the factory with configuration
const computeFactory = ComputeFactory.build("ResourceId", {
  type: "ecs",
  ecsType: "fargate",
  // ... configuration
});

// Step 2: Deploy in your stack
const cluster = computeFactory(app, this);

// Step 3: Access connections for security group management
cluster.connections.allowTo(database, Port.tcp(5432));
The factory pattern automatically:
  • Selects the appropriate ECS implementation based on ecsType
  • Injects default VPC and ECR repository from the app context
  • Configures protocol (HTTP/HTTPS) based on domainConfig presence
  • Creates security group connections for network management

Network Architecture

Security Group Management

All ECS deployments implement the IConnectable interface:
const compute = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo,
  connections: [database, redisCluster]  // Automatic TCP connections
});

const cluster = compute(app, this);

// Manual connection management
cluster.connections.allowTo(
  otherResource,
  Port.tcp(8080),
  "Allow traffic to other resource"
);

Database Connectivity

Database connections can be configured through the connections property or via the IConnectable interface:
import { RdsAurora } from "@fjall/components-infrastructure/lib/resources/aws/database/rds-aurora";

const database = new RdsAurora(this, "Database", {
  clusterName: "app-db",
  vpc: vpc
});

// Option 1: Using connections property
const compute = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo,
  connections: [database]  // Automatically opens all TCP ports
});

// Option 2: Manual connection after deployment
const cluster = compute(app, this);
cluster.connections.allowTo(database, Port.tcp(5432));
Note: The databaseConnection property exists in the underlying FargateClusterProps interface but is NOT exposed through ComputeFactory. Use the connections array or manual connection management instead.

Container Configuration

Environment Variables

Pass configuration to your container application:
const compute = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo,
  containerEnvironment: {
    NODE_ENV: "production",
    LOG_LEVEL: "info",
    API_VERSION: "v2",
    DATABASE_HOST: "db.example.com"
  }
});

Secrets Management

Import secrets from AWS Secrets Manager using the containerSecretsImport property:
const compute = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo,
  containerSecretsImport: {
    DATABASE_URL: {
      id: "DatabaseUrlSecret",
      name: "prod/database-url",
      field: "connectionString"  // Extract specific JSON field
    },
    API_KEY: {
      id: "ApiKeySecret",
      name: "prod/api-key",
      field: "key"
    }
  }
});
The secrets are automatically:
  • Retrieved from AWS Secrets Manager at runtime
  • Decrypted using KMS
  • Injected as environment variables in the container
  • Kept secure (never logged or exposed)
SecretImport Type:
type SecretImport = {
  id: string;      // Unique identifier for the secret import
  name: string;    // Name of the secret in Secrets Manager
  field: string;   // JSON field to extract from the secret
}

Container Entry Point

For Free Tier and Spot deployments, the container entry point is hardcoded to ["node", "main.js"]. Fargate deployments use the default entry point from the Docker image. If you need a custom entry point, you must use the underlying class directly (not through ComputeFactory) and pass the containerCommand property.

Default Configuration

All ECS deployments include these defaults:

Fargate Defaults

  • Container Insights: V2 enabled for enhanced monitoring
  • Log retention: 14 days in CloudWatch Logs with prefix /ecs/{clusterName}/{serviceName}
  • Deployment circuit breaker: Enabled with automatic rollback
  • Health percentages: Min 100%, Max 200% during deployments
  • Execute command: Enabled for SSH-like container access
  • Health check interval: 120 seconds with 10-second timeout
  • ECS-managed tags: Propagated from service to tasks
  • Desired count: Fixed at 2 tasks (not configurable via ComputeFactory)

Free Tier Defaults

  • Instance: t3.micro (single instance)
  • Memory: 400 MiB
  • Entry point: [“node”, “main.js”]
  • Health check interval: 30 seconds with 15-second timeout
  • Desired count: 1 task

Spot Defaults

  • Instance: m8g.large ARM (2-3 instances)
  • Memory: 1024 MiB
  • CPU: 1 unit
  • Entry point: [“node”, “main.js”]
  • Desired count: 3 tasks
  • Health check interval: 30 seconds with 15-second timeout
  • Capacity rebalancing: Enabled

Usage Patterns

Pattern 1: Basic Fargate Application

import { ComputeFactory } from "@fjall/components-infrastructure";
import * as ecr from "aws-cdk-lib/aws-ecr";

const repo = ecr.Repository.fromRepositoryName(this, "Repo", "my-app");

const compute = ComputeFactory.build("WebApp", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 3000,
  ecrRepository: repo
});

const cluster = compute(app, this);

Pattern 2: HTTPS API with Custom Domain

const compute = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo,
  domainConfig: {
    domainName: "api.example.com"
    // Certificate and hosted zone auto-created
    // Protocol automatically set to HTTPS
  }
});

const cluster = compute(app, this);

Pattern 3: Container with Environment and Secrets

const compute = ComputeFactory.build("AppCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo,
  containerEnvironment: {
    NODE_ENV: "production",
    LOG_LEVEL: "info"
  },
  containerSecretsImport: {
    DATABASE_URL: {
      id: "DbSecret",
      name: "prod/db-url",
      field: "connectionString"
    },
    API_KEY: {
      id: "ApiKey",
      name: "prod/api-key",
      field: "key"
    }
  }
});

Pattern 4: Database-Connected Application

import { RdsAurora } from "@fjall/components-infrastructure/lib/resources/aws/database/rds-aurora";
import { Vpc } from "@fjall/components-infrastructure/lib/resources/aws/networking/vpc";

const vpc = new Vpc(this, "Vpc", {
  vpcName: "app-vpc"
});

const database = new RdsAurora(this, "Database", {
  clusterName: "app-db",
  vpc: vpc
});

const compute = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo,
  vpc: vpc,
  connections: [database],  // Automatic security group configuration
  containerSecretsImport: {
    DATABASE_URL: {
      id: "DatabaseSecret",
      name: database.secret.secretName,
      field: "connectionString"
    }
  }
});

Pattern 5: Cost-Optimized with Spot Instances

const compute = ComputeFactory.build("SpotCompute", {
  type: "ecs",
  ecsType: "spot",  // Uses m8g.large ARM instances
  containerPort: 8080,
  ecrRepository: repo,
  containerEnvironment: {
    NODE_ENV: "production"
  }
});

// Runs 3 tasks on 2-3 m8g.large Spot instances
// Approximately 70% cost savings vs Fargate

Pattern 6: Development Environment with Free Tier

const compute = ComputeFactory.build("DevCompute", {
  type: "ecs",
  ecsType: "freetier",  // Uses single t3.micro instance
  containerPort: 3000,
  ecrRepository: repo,
  containerEnvironment: {
    NODE_ENV: "development"
  }
});

// Eligible for AWS Free Tier: 750 hours/month free

Auto-Scaling Configuration

IMPORTANT: Auto-scaling is NOT automatically configured in Fjall ECS clusters. While the addScalingPolicy method exists in the FargateCluster class, it is NEVER called during construction. If you need auto-scaling, you must:
  1. Use the underlying FargateCluster class directly (not through ComputeFactory)
  2. Manually call the addScalingPolicy method after instantiation
Auto-scaling specs (if manually configured):
  • Min capacity: 2 tasks
  • Max capacity: 10 tasks
  • Target value: 50% utilization
  • Scale out cooldown: 60 seconds
  • Scale in cooldown: 60 seconds
  • Metrics: CPU or Memory utilization
This is a limitation of the current implementation and should be considered when planning for production workloads.

Security Configuration

IAM Roles

All ECS deployments create two IAM roles: Execution Role (used by ECS agent):
  • AmazonECSTaskExecutionRolePolicy (managed)
  • Secrets Manager read access: secretsmanager:GetSecretValue, secretsmanager:DescribeSecret
  • KMS decryption: kms:Decrypt
  • SSM Session Manager: ssmmessages:* for ECS Exec
Task Role (used by container application):
  • Custom inline policies (not available via ComputeFactory)
  • AWS managed policies (not available via ComputeFactory)
Note: The taskRoleInlinePolicies and taskRoleManagedPolicies properties exist in the underlying FargateClusterProps but are NOT exposed through ComputeFactory. To add custom IAM policies, you must use the underlying class directly.

Security Groups

const compute = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo
});

const cluster = compute(app, this);

// Allow Redis connection
cluster.connections.allowTo(
  redisCluster,
  ec2.Port.tcp(6379),
  "Allow Redis access"
);

// Allow outbound HTTPS
cluster.connections.allowTo(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(443),
  "Allow HTTPS outbound"
);

Cost Optimization

Strategy 1: Use EC2 Spot Instances

EC2 Spot can reduce costs by up to 70% compared to Fargate:
const spotCompute = ComputeFactory.build("SpotCompute", {
  type: "ecs",
  ecsType: "spot",  // m8g.large ARM instances
  containerPort: 8080,
  ecrRepository: repo
});
Spot Instance Benefits:
  • 70% cost reduction vs Fargate
  • ARM architecture (Graviton) for additional performance/cost benefits
  • Automatic capacity rebalancing handles interruptions
  • 2-3 instances with 3 tasks for high availability

Strategy 2: Use Free Tier for Development

const devCompute = ComputeFactory.build("DevCompute", {
  type: "ecs",
  ecsType: "freetier",  // Single t3.micro instance
  containerPort: 3000,
  ecrRepository: repo
});
Free Tier Benefits:
  • Eligible for 750 hours/month free for first 12 months
  • Single t3.micro instance with minimal overhead
  • 400 MiB memory optimized for small workloads

Strategy 3: Right-Size Fargate Resources

Fargate charges per vCPU and memory used. Start minimal and scale based on CloudWatch metrics:
// Start with minimal resources
const compute = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo
});

// Monitor CloudWatch Container Insights
// Adjust CPU/memory in underlying FargateClusterProps if needed

Advanced Configuration

Custom Health Checks

Health check configuration is set at the load balancer target group level: Fargate Health Checks:
  • Interval: 120 seconds
  • Timeout: 10 seconds
  • Path: ”/” (default, not configurable via ComputeFactory)
  • Healthy threshold: 2 consecutive successes
  • Unhealthy threshold: 2 consecutive failures
Free Tier/Spot Health Checks:
  • Interval: 30 seconds
  • Timeout: 15 seconds
  • Path: ”/” (default, not configurable via ComputeFactory)
  • Healthy threshold: 3 consecutive successes
  • Unhealthy threshold: 3 consecutive failures
Note: The healthCheckPath property exists in the underlying props but is NOT exposed through ComputeFactory.

Protocol Configuration

Protocol is automatically determined based on domain configuration:
// HTTP (no domain)
const httpCompute = ComputeFactory.build("HttpApi", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 80,
  ecrRepository: repo
  // No domainConfig = HTTP protocol
});

// HTTPS (with domain)
const httpsCompute = ComputeFactory.build("HttpsApi", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 80,
  ecrRepository: repo,
  domainConfig: {
    domainName: "api.example.com"
  }
  // domainConfig present = HTTPS protocol (line 272 of compute.ts)
});

Methods and Properties

Public Properties

connections

Access the security group connections for the ECS service:
const compute = ComputeFactory.build("ApiCompute", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo
});

const cluster = compute(app, this);

// Allow Redis connection
cluster.connections.allowTo(
  redisCluster,
  ec2.Port.tcp(6379),
  "Allow Redis access"
);
Returns: Connections - CDK Connections object for security group management

Complete Example

import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import { ComputeFactory } from "@fjall/components-infrastructure";
import { Vpc } from "@fjall/components-infrastructure/lib/resources/aws/networking/vpc";
import { RdsAurora } from "@fjall/components-infrastructure/lib/resources/aws/database/rds-aurora";
import * as ecr from "aws-cdk-lib/aws-ecr";

export class ProductionApiStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // Network infrastructure
    const vpc = new Vpc(this, "Vpc", {
      vpcName: "production-vpc",
      maxAzs: 3,
      natGateways: 3
    });

    // Database
    const database = new RdsAurora(this, "Database", {
      clusterName: "api-database",
      vpc: vpc,
      instanceType: "serverless-v2",
      minCapacity: 0.5,
      maxCapacity: 2
    });

    // ECR repository
    const repository = ecr.Repository.fromRepositoryName(
      this,
      "ApiRepo",
      "production-api"
    );

    // Production Fargate cluster
    const computeFactory = ComputeFactory.build("ProductionApi", {
      type: "ecs",
      ecsType: "fargate",
      containerPort: 8080,
      ecrRepository: repository,
      vpc: vpc,

      // Environment configuration
      containerEnvironment: {
        NODE_ENV: "production",
        LOG_LEVEL: "info",
        PORT: "8080"
      },

      // Secrets from Secrets Manager
      containerSecretsImport: {
        DATABASE_URL: {
          id: "DatabaseUrlSecret",
          name: database.secret.secretName,
          field: "connectionString"
        }
      },

      // HTTPS with custom domain
      domainConfig: {
        domainName: "api.example.com"
      },

      // Database connectivity
      connections: [database]
    });

    // Deploy the cluster
    const cluster = computeFactory(app, this);

    // Additional security group rules if needed
    cluster.connections.allowTo(
      redisCluster,
      ec2.Port.tcp(6379),
      "Allow Redis access"
    );
  }
}

Multi-Environment Example

import { ComputeFactory } from "@fjall/components-infrastructure";

// Development with Free Tier
const devCompute = ComputeFactory.build("DevApi", {
  type: "ecs",
  ecsType: "freetier",
  containerPort: 3000,
  ecrRepository: repo,
  containerEnvironment: {
    NODE_ENV: "development"
  }
});

// Staging with Spot Instances
const stagingCompute = ComputeFactory.build("StagingApi", {
  type: "ecs",
  ecsType: "spot",
  containerPort: 8080,
  ecrRepository: repo,
  containerEnvironment: {
    NODE_ENV: "staging"
  },
  domainConfig: {
    domainName: "staging-api.example.com"
  }
});

// Production with Fargate
const prodCompute = ComputeFactory.build("ProdApi", {
  type: "ecs",
  ecsType: "fargate",
  containerPort: 8080,
  ecrRepository: repo,
  containerEnvironment: {
    NODE_ENV: "production"
  },
  domainConfig: {
    domainName: "api.example.com"
  }
});

Best Practices

  1. Use ComputeFactory for all ECS deployments - The factory pattern is the supported API. Direct class imports are not exported.
  2. Choose the right deployment mode - Fargate for production, Spot for cost-optimization, Free Tier for development.
  3. Understand task count limitations - Fargate is fixed at 2 tasks. Auto-scaling is not automatically configured.
  4. Use Secrets Manager for sensitive data - Never use containerEnvironment for passwords or API keys. Always use containerSecretsImport.
  5. Configure custom domains for production - The domainConfig property automatically enables HTTPS and creates certificates.
  6. Monitor with Container Insights - Automatically enabled by default. Provides deep visibility into container metrics.
  7. Use the connections property for database access - Automatically configures security group rules.
  8. Implement proper health checks - Default health check path is ”/”. Ensure your application responds with 200 OK.
  9. Use ARM architecture for cost savings - Spot instances use m8g.large ARM processors for better price/performance.
  10. Plan for Spot interruptions - Use Spot instances only for fault-tolerant workloads. Capacity rebalancing is automatic.

Common Patterns

Pattern 1: Microservices Architecture

Deploy multiple services with shared infrastructure:
const services = ["auth", "users", "orders", "payments"];

services.forEach(service => {
  const compute = ComputeFactory.build(`${service}Service`, {
    type: "ecs",
    ecsType: "fargate",
    containerPort: 8080,
    ecrRepository: ecr.Repository.fromRepositoryName(this, `${service}Repo`, service),
    vpc: sharedVpc,
    connections: [sharedDatabase]
  });

  compute(app, this);
});
When to use: Building domain-driven microservices with independent scaling and deployment.

Pattern 2: Multi-Environment Setup

const environments = [
  { name: "dev", ecsType: "freetier" },
  { name: "staging", ecsType: "spot" },
  { name: "production", ecsType: "fargate" }
];

environments.forEach(env => {
  const compute = ComputeFactory.build(`${env.name}Api`, {
    type: "ecs",
    ecsType: env.ecsType as "fargate" | "freetier" | "spot",
    containerPort: 8080,
    ecrRepository: repo,
    containerEnvironment: {
      NODE_ENV: env.name
    }
  });

  compute(app, this);
});
When to use: Managing multiple environments with different cost/performance profiles.

Pattern 3: Scheduled Background Jobs

Use Spot instances for fault-tolerant batch processing:
const batchCompute = ComputeFactory.build("BatchProcessor", {
  type: "ecs",
  ecsType: "spot",  // Cost-optimized for batch workloads
  containerPort: 8080,
  ecrRepository: repo,
  connections: [database, s3Bucket]
});
When to use: Background jobs, data processing, or other fault-tolerant workloads.

Cost Considerations

ComponentCostOptimization
Fargate tasks0.04048/vCPU/hour+0.04048/vCPU/hour + 0.004445/GB/hourFixed at 2 tasks with 256 CPU / 512 MB (default). Not configurable via ComputeFactory.
Application Load Balancer0.0225/hour+0.0225/hour + 0.008/LCU-hourOne ALB per deployment. Consider path-based routing to share ALBs.
NAT Gateway0.045/hour+0.045/hour + 0.045/GB processedRequired for private subnet container communication.
CloudWatch Logs0.50/GBingested+0.50/GB ingested + 0.03/GB stored14-day retention (default). Monitor log volume.
EC2 Spot (ecsSpot)~70% discount vs FargateUses m8g.large ARM instances. 2-3 instances with 3 tasks.
Free Tier (ecsFreeTier)Eligible for 750 hours/month t3.microSingle instance. 400 MiB memory. Development only.
Example monthly costs (us-east-1):
  • Dev (Free Tier, 1 t3.micro): ~$0-15/month (free tier eligible)
  • Staging (Spot, 2-3 m8g.large): ~$30-50/month
  • Production (Fargate, 2 tasks, 256 CPU, 512 MB): ~$30/month

Troubleshooting

Common Issues

  1. Cannot import FargateCluster, EcsFreeTier, or EcsSpot classes
    • Cause: These classes are not exported from the main package
    • Solution: Use ComputeFactory from @fjall/components-infrastructure instead. This is the only supported API.
  2. Task count is always 2, regardless of desiredCount configuration
    • Cause: The desiredCount value is hardcoded to 2 in line 340 of ecs.ts
    • Solution: This is a known limitation. Fargate deployments always run 2 tasks. Consider using Spot instances (3 tasks) or the underlying class directly if you need different task counts.
  3. Auto-scaling is not working
    • Cause: The addScalingPolicy method is never called during construction
    • Solution: Auto-scaling is not automatically configured in Fjall. You must use the underlying FargateCluster class directly and manually call addScalingPolicy after instantiation.
  4. Protocol is always HTTP, even though I want HTTPS
    • Cause: Protocol is determined by the presence of domainConfig
    • Solution: Add a domainConfig object with your domain name. Protocol will automatically be set to HTTPS (line 272 of compute.ts).
  5. Cannot configure health check path
    • Cause: The healthCheckPath property is not exposed through ComputeFactory
    • Solution: This is a limitation of the factory pattern. Use the underlying class directly if you need custom health check paths.
  6. Deployment stuck at “in progress” with no new tasks
    • Cause: Insufficient memory/CPU or image pull errors
    • Solution: Check CloudWatch Logs for OOM errors or ECR authentication issues. Verify ECR image exists and is accessible.
  7. Cannot connect to RDS database from container
    • Cause: Security group rules not configured
    • Solution: Add the database to the connections array or manually configure security groups via cluster.connections.allowTo().
  8. Free Tier deployment fails with entry point error
    • Cause: Container expects different entry point than hardcoded ["node", "main.js"]
    • Solution: Ensure your Docker image has a main.js file or use the underlying class directly to customize the entry point.
  9. Spot instance tasks keep restarting
    • Cause: Spot instance interruptions or insufficient memory
    • Solution: Verify your application can handle interruptions gracefully. Check CloudWatch Logs for memory errors. Spot deployments have 1024 MiB memory allocated.

Debug Commands

# List all ECS clusters
aws ecs list-clusters

# Describe cluster status
aws ecs describe-clusters --clusters {ClusterName}Cluster

# List services in cluster
aws ecs list-services --cluster {ClusterName}Cluster

# Describe service with deployment status
aws ecs describe-services \
  --cluster {ClusterName}Cluster \
  --services {ClusterName}ServiceService

# View service events (deployment history)
aws ecs describe-services \
  --cluster {ClusterName}Cluster \
  --services {ClusterName}ServiceService \
  --query 'services[0].events[:10]'

# List running tasks
aws ecs list-tasks \
  --cluster {ClusterName}Cluster \
  --service-name {ClusterName}ServiceService

# Get task details and status
aws ecs describe-tasks \
  --cluster {ClusterName}Cluster \
  --tasks arn:aws:ecs:region:account:task/task-id

# View container logs
aws logs tail /ecs/{ClusterName}Cluster/{ClusterName}ServiceService --follow

# Execute command in running container (ECS Exec)
aws ecs execute-command \
  --cluster {ClusterName}Cluster \
  --task arn:aws:ecs:region:account:task/task-id \
  --container {ClusterName}ServiceService \
  --interactive \
  --command "/bin/sh"

# View load balancer health
aws elbv2 describe-target-health \
  --target-group-arn arn:aws:elasticloadbalancing:region:account:targetgroup/name

# Check Container Insights metrics
aws cloudwatch get-metric-statistics \
  --namespace ECS/ContainerInsights \
  --metric-name CpuUtilized \
  --dimensions Name=ClusterName,Value={ClusterName}Cluster \
  --start-time 2024-01-01T00:00:00Z \
  --end-time 2024-01-01T23:59:59Z \
  --period 3600 \
  --statistics Average

See Also