_SH Log's
Back to Root
EST: 5 min read

Microservices as One Engineer (Go + AWS + Terraform)

Microservices for one engineer pay off when you have the right tooling — Go services, Terraform IaC, and AWS ECS Fargate. Here's how I make it work solo.

#go#aws#terraform#microservices

The common advice is "don't use microservices if you're a small team." I disagree — if your tooling is right, microservices for one engineer are manageable and often worth it. Here's the exact setup I use to run six products as microservices with no ops team.

When microservices make sense solo

The break-even point isn't team size — it's deployment independence and failure isolation. If LetX's compile service goes down, it shouldn't take the CRDT sync server with it. If QuantumSketch's Manim renderer is chewing CPU, it shouldn't affect API latency for other products.

For me: separate deployable units per concern, with a shared infrastructure layer.

The module structure

services/
├── letx-api/           # Go: auth, document CRUD, user management
├── letx-collab/        # Go: CRDT sync, WebSocket sessions
├── letx-compiler/      # Go+Docker: LaTeX compilation sandbox
├── quantumsketch-api/  # Go: prompt intake, job queue
├── quantumsketch-worker/ # Python: Manim render, TTS, ffmpeg
├── bikroybuddy-api/    # Go: WhatsApp webhook, search
├── context-heavy/      # Go: knowledge graph API
└── shared/
    ├── auth/           # JWT middleware (Go library)
    ├── db/             # pgx pool config
    └── observability/  # OpenTelemetry setup

Shared libraries are internal Go modules — not microservices themselves, but packages imported by services. No RPC between shared libraries and services; they're compiled in.

Terraform: infrastructure as code

Every service is deployed via a shared Terraform module:

module "letx_collab" {
  source = "../../modules/ecs-service"

  name          = "letx-collab"
  image         = "${var.ecr_base}/letx-collab:${var.image_tag}"
  cpu           = 256
  memory        = 512
  port          = 8080
  desired_count = 2

  environment = {
    DB_DSN      = var.db_dsn
    REDIS_URL   = var.redis_url
    JWT_SECRET  = var.jwt_secret
  }

  health_check_path = "/health"
  alb_listener_arn  = aws_alb_listener.main.arn
  alb_priority      = 10
}

Adding a new service is 10 lines of HCL. No console clicking. terraform plan shows exactly what changes before apply.

Service communication

I keep inter-service communication simple:

| Pattern | Used for | |---------|----------| | HTTP/JSON | Synchronous requests between services | | SQS | Async work queues (compile jobs, render jobs) | | WebSocket (direct) | Real-time CRDT sync (letx-collab only) | | Shared PostgreSQL | Read-heavy cross-service queries | | Redis pub/sub | Real-time notifications (lightweight) |

No gRPC. For a solo engineer, the complexity of proto files, codegen, and versioning is not worth the marginal performance gain over HTTP/JSON between services that all live in the same AWS region.

CI/CD per service

Each service has its own GitHub Actions workflow:

# .github/workflows/letx-collab.yml
on:
  push:
    paths:
      - 'services/letx-collab/**'
      - 'shared/**'

jobs:
  deploy:
    steps:
      - name: Build and push Docker image
        run: |
          docker build -t $ECR_REPO:$SHA services/letx-collab/
          docker push $ECR_REPO:$SHA
      
      - name: Update ECS service
        run: |
          aws ecs update-service \
            --cluster prod \
            --service letx-collab \
            --force-new-deployment

Path filtering means touching letx-collab/ only triggers letx-collab deployment. No global deploys from a single change.

Observability without an ops team

Three tools, all low-maintenance:

  1. AWS CloudWatch — ECS metrics (CPU, memory, task count). Free tier covers everything at my scale.
  2. OpenTelemetry → Grafana Cloud — distributed traces. Grafana Cloud free tier: 14-day retention, sufficient for debugging.
  3. Structured JSON logs — every service logs JSON to stdout. CloudWatch Logs Insights can query across all services.
// shared/observability/log.go
func Info(ctx context.Context, msg string, fields ...slog.Attr) {
    slog.LogAttrs(ctx, slog.LevelInfo, msg,
        append(fields,
            slog.String("service", ServiceName),
            slog.String("trace_id", TraceID(ctx)),
        )...)
}

What breaks at solo-scale

Database connections. 8 services × 10 connection pool size = 80 connections minimum. RDS db.t3.small maxes at 40. Fix: pgBouncer in transaction mode, or use RDS Proxy.

Terraform state conflicts. Two fast deploys of different services can conflict on the Terraform state lock. Fix: separate Terraform state per service using S3 backend with DynamoDB locking.

Runaway ECS tasks. A bug that causes task crash-looping triggers ECS to spin up replacement tasks — burning CPU and costing money. Fix: CloudWatch alarm on ECS CrashCount > 3 → auto-stop the service + notify Slack.

FAQ

Are microservices worth it for a solo developer? Yes, if you have the right tooling (Terraform, ECS, GitHub Actions) and your services have genuine deployment independence. Without tooling, microservices are overhead; with it, they provide valuable failure isolation.

What's the alternative to microservices for solo founders? A well-structured monolith with clear module boundaries is the right choice for most solo founders starting out. Migrate to microservices when deployment independence or failure isolation becomes a real pain point.

How do you handle service discovery? AWS ALB routes by hostname (letx-api.internal, letx-collab.internal). Services call each other by internal hostname. No service mesh needed at this scale.

What's the minimum AWS spend for this setup? ~$80–120/month for a production microservices setup on ECS Fargate with RDS PostgreSQL (db.t3.micro) and ALB.


Written by Shihab Shahriar Antor — AI Engineer & Founder of Shahriar Labs. See also: Terraform on AWS: Infrastructure as Code Guide · The Solo Founder Stack.