Skip to main content

Overview

The Tinkerer pattern targets experimentation and learning while staying within AWS free tier limits. It runs ECS on a single EC2 instance with direct access (no Application Load Balancer), keeping costs as low as possible.

What’s Included

fjall create app --type tinkerer
Creates:
  • ECS on EC2 (t4g.micro instance)
  • Direct access (no Application Load Balancer)
  • RDS Instance database (t4g.micro)
  • Basic networking setup

Architecture

┌─────────────────────┐
│  ECS on EC2         │
│  (t4g.micro)        │ Direct port mapping (no ALB)
│  Memory: 400 MiB    │
└────────┬────────────┘
         |
┌────────┴────────┐
│  RDS Instance   │ (t4g.micro, free tier eligible)
└─────────────────┘

Generated Infrastructure

#!/usr/bin/env node

import {
  App,
  DatabaseFactory,
  ComputeFactory,
  getConfig,
} from "@fjall/components-infrastructure";

const appName = "experiment";
const app = App.getApp(appName);

app.addTags({
  "fjall:costAllocation:owner": "engineering",
});

const experimentStorage = app.addDatabase(
  DatabaseFactory.build("ExperimentStorage", {
    type: "Instance",
    databaseName: "ExperimentDatabase",
    instanceType: "t4g.micro", // Free tier eligible
    allocatedStorage: 20, // Free tier: up to 20 GB
  }),
);

app.addCompute(
  ComputeFactory.build("ExperimentCompute", {
    type: "ecs",
    ecrRepository: app.getDefaultContainerRegistry(),
    cluster: {
      directAccess: true, // No ALB, direct port mapping
    },
    services: [
      {
        name: "app",
        capacityProvider: "EC2",
        memoryLimitMiB: 400,
        containers: [
          {
            port: 3000,
            environment: {
              ENVIRONMENT: getConfig().environment,
              DATABASE_HOST: experimentStorage.getHostEndpoint(),
              DATABASE_PORT: `${experimentStorage.getHostPort()}`,
              DATABASE_NAME: experimentStorage.getDatabaseName(),
            },
            secretsImport: {
              DATABASE_PASSWORD: experimentStorage
                .getCredentials()
                .getImport("password"),
            },
          },
        ],
      },
    ],
  }),
);

Free Tier Limits

ServiceFree Tier AllowanceTinkerer Usage
EC2750 hrs/month t4g.micro720 hrs (24/7)
RDS750 hrs/month t4g.micro720 hrs (24/7)
EBS30 GB storage~8 GB
Data Transfer100 GB/monthVaries
Running 24/7 uses 720 hours, staying within the 750-hour limit with buffer for restarts.

Specifications

Compute (ECS on EC2)

  • Instance type: t4g.micro (ARM-based, free tier eligible)
  • Memory: 400 MiB allocated to the container
  • Access: Direct port mapping (no Application Load Balancer)
  • Auto-scaling: Disabled (single instance)
  • Availability: 2 AZ

Database (RDS Instance)

  • Instance: t4g.micro
  • Engine: PostgreSQL 17.5
  • Storage: 20 GB gp2 (free tier max)
  • Backup: 1-day retention
  • Multi-AZ: Not available

Networking

  • VPC: Default configuration
  • Subnets: Public and private
  • NAT: None (public subnets only)
  • Security groups: Basic rules

Cost Breakdown

Monthly cost: $0 (within free tier) Potential charges if exceeding limits:
  • Extra EC2 hours: ~$0.0084/hour (t4g.micro)
  • Extra RDS hours: ~$0.016/hour
  • Data transfer over 100 GB: $0.09/GB
  • EBS storage over 30 GB: $0.10/GB/month

Use Cases

Perfect for:
  • Learning AWS and Fjall
  • Personal projects
  • Proof of concepts
  • Development environments
  • Hackathon projects
  • Portfolio applications
Not suitable for:
  • Production workloads
  • High-traffic applications
  • Multi-AZ requirements
  • Large databases
  • CPU-intensive tasks

Customisation Options

Environment Variables

app.addCompute(
  ComputeFactory.build("ExperimentCompute", {
    type: "ecs",
    cluster: {
      directAccess: true,
    },
    services: [
      {
        name: "app",
        capacityProvider: "EC2",
        memoryLimitMiB: 400,
        containers: [
          {
            environment: {
              NODE_ENV: "development",
              API_KEY: "dev-key-123",
              FEATURE_FLAGS: "experimental",
            },
          },
        ],
      },
    ],
  }),
);

Adding Lambda Functions

Lambda has a free tier of 1M requests per month:
fjall add compute --app experiment --name Worker --type lambda

Monitoring Your Usage

CloudWatch Dashboard

Monitor free tier usage:
  • EC2 instance hours
  • RDS instance hours
  • Data transfer
  • Storage usage

Scaling Beyond Free Tier

When ready to scale:

Option 1: Upgrade in Place

# Move to a Lightweight pattern with Fargate
fjall create app --name production --type lightweight

Option 2: Create New App

# Create a production app with the Standard pattern
fjall create app --name production --type standard
# Migrate data and switch traffic

Development Tips

Local Development

Use Docker Compose to mirror the setup locally:
version: "3.8"
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_HOST=db
      - DATABASE_PORT=5432
      - DATABASE_NAME=experiment
      - DATABASE_PASSWORD=localpass
    depends_on:
      - db

  db:
    image: postgres:17-alpine
    environment:
      - POSTGRES_DB=experiment
      - POSTGRES_PASSWORD=localpass

Resource Constraints

The t4g.micro instance has limited resources:
  • Develop locally
  • Deploy only tested code
  • Use minimal dependencies
  • Optimise container size

Common Issues

Out of Memory

The container is limited to 400 MiB:
  • Reduce container memory usage
  • Optimise application code
  • Remove unused dependencies
  • Use lightweight base images

Database Connection Limits

t4g.micro has limited connections:
  • Use connection pooling
  • Close idle connections
  • Implement retry logic

Best Practices

  1. Monitor usage daily during development
  2. Set billing alerts at 1,1, 5, $10
  3. Stop resources when not in use
  4. Use Lambda for sporadic workloads
  5. Optimise images to reduce storage
  6. Clean up unused resources

Breaking Out the Pattern

When you are ready to move beyond the Tinkerer defaults, you can decompose the pattern into individual factory calls for full control over each component. See the individual factory documentation for details:

Next Steps

Deploy your application

Ship the Tinkerer stack to AWS.

Add resources

Extend the application with databases, compute, storage, and more.

Upgrade to Lightweight

Move to Fargate when you outgrow the single instance.

Upgrade to Standard

Run a production-ready, multi-AZ stack.