Skip to content

Guide: Configuring a Parent Stack for AWS ECS Fargate & MongoDB Atlas with Simple Container

This guide is for DevOps teams who want to configure a parent stack (server.yaml) for deploying infrastructure on AWS ECS Fargate and MongoDB Atlas using Simple Container.

With this setup, developers can deploy microservices to ECS Fargate while using MongoDB Atlas as a managed database.


Prerequisites

Before configuring the parent stack, ensure that:

You have an AWS account & IAM credentials with permissions to create ECS Fargate clusters.

You have a MongoDB Atlas account with a valid API key.

Simple Container is installed:

curl -s "https://dist.simple-container.com/sc.sh" | bash

Setting Up AWS & MongoDB Atlas Secrets

AWS and MongoDB Atlas credentials must be stored in secrets.yaml.

Step 1: Define secrets.yaml

Create the .sc/stacks/devops/secrets.yaml file to store AWS credentials & MongoDB Atlas API keys:

---
# File: "myproject/.sc/stacks/devops/secrets.yaml"

schemaVersion: 1.0

auth:
  aws:
    type: aws-token
    config:
      accessKey: "AKIAIOSFODNN7EXAMPLE"
      secretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
      region: "us-west-2"

values:
  CLOUDFLARE_API_TOKEN: "abcdefgh123456789"
  MONGODB_ATLAS_PUBLIC_KEY: "public-key-123"
  MONGODB_ATLAS_PRIVATE_KEY: "private-key-456"

Ambient credentials (OIDC / instance profile): accessKey and secretAccessKey are optional. Omit them (keep region) and sc falls back to the standard AWS credential chain — GitHub Actions OIDC web-identity, an EC2/ECS instance profile, or the AWS_* environment — so CI can assume a short-lived federated role instead of storing long-lived keys in secrets.yaml.

What This Does

Stores AWS credentials for programmatic access.

Saves MongoDB Atlas API keys for provisioning databases.


Configuring Infrastructure Provisioning (server.yaml)

Now, define .sc/stacks/devops/server.yaml to provision ECS Fargate & MongoDB Atlas.

Step 2: Define server.yaml

---
# File: "myproject/.sc/stacks/devops/server.yaml"
schemaVersion: 1.0

# Provisioning state management
provisioner:
  type: pulumi
  config:
    state-storage:
      type: s3-bucket
      config:
        credentials: "${auth:aws}"
        bucketName: myproject-sc-state
    secrets-provider:
      type: aws-kms
      config:
        credentials: "${auth:aws}"
        keyName: myproject-sc-kms-key

# Deployment templates for ECS Fargate-based workloads
templates:
  stack-per-app:
    type: ecs-fargate
    config:
      credentials: "${auth:aws}"
      account: "${auth:aws.projectId}"

# Infrastructure resources provisioned inside AWS & MongoDB Atlas
resources:
  registrar:
    type: cloudflare  # Optional DNS management
    config:
      credentials: "${secret:CLOUDFLARE_API_TOKEN}"
      accountId: "89cc23bd273c76d6767f6566c54621c2"
      zoneName: "myproject.com"

  resources:
    staging:
      template: stack-per-app
      resources:
        mongodb:
          type: mongodb-atlas
          config:
            admins: [ "admin" ]
            developers: [ "developer1", "developer2" ]
            instanceSize: "M10"
            orgId: "878cd82332ff12c2332d2234"
            region: "US_WEST_2"
            cloudProvider: AWS
            privateKey: "${secret:MONGODB_ATLAS_PRIVATE_KEY}"
            publicKey: "${secret:MONGODB_ATLAS_PUBLIC_KEY}"
            backup:
              every: 4h
              retention: 24h

What This Does

Configures Pulumi for managing state in an S3 bucket.

Uses AWS KMS to encrypt secrets.

Defines an ECS Fargate template (stack-per-app) for developers to deploy microservices.

Provisions MongoDB Atlas, making it available for microservices.


Optional: Cap Workload Roles with a Permissions Boundary

Set permissionsBoundary on a template's config to an IAM policy ARN. Simple Container then attaches it as the permissions boundary on every IAM role it creates for workloads under that template:

  • ECS task and execution roles
  • Lambda execution roles (including alert / CloudTrail-security-alert Lambdas)
  • DB-init (pg-init / mysql-init) task roles
templates:
  stack-per-app:
    type: ecs-fargate
    config:
      credentials: "${auth:aws}"
      account: "${auth:aws.projectId}"
      permissionsBoundary: "arn:aws:iam::123456789012:policy/sc-workload-boundary"

A permissions boundary is a ceiling: a role's effective permissions become the intersection of its own policy and the boundary. This lets an operator provision SC workloads under a least-privilege deploy identity while guaranteeing a hard limit on what any minted role can ever do.

Defaults and compatibility. The field is optional and empty by default — leave it unset and nothing changes. Adding or changing it later is an in-place role update (PutRolePermissionsBoundary), not a role replacement, so existing deployments are not recreated.

Precedence. Declare it either at the template level (as above) or inside the ${auth:...} credentials block. A non-empty template-level value wins.

Warning: one ARN caps all workload roles for the template at once. A boundary that is too tight silently strips permissions (ECR pull, CloudWatch Logs, Secrets Manager, …) from every service on the next deploy. Validate the boundary in staging before rolling it to production.


Provisioning the AWS & MongoDB Atlas Parent Stack

Once server.yaml is configured, provision the infrastructure:

sc provision -s devops

What This Does

Creates an S3 bucket for state storage.

Deploys MongoDB Atlas with provisioned users.

Configures Cloudflare DNS (optional).

Prepares ECS Fargate infrastructure for microservices.


Deploying Microservices to ECS Fargate

Once the infrastructure is provisioned, developers can deploy their microservices.

Step 1: Define client.yaml for a Microservice

---
# File: "myproject/.sc/stacks/myservice/client.yaml"

schemaVersion: 1.0

stacks:
  staging:
    type: cloud-compose
    parent: myproject/devops
    config:
      domain: ${env:MY_SERVICE_DOMAIN}
      dockerComposeFile: ./docker-compose.yaml
      uses:
        - mongodb
      runs:
        - myservice
      size:
        cpu: 512
        memory: 1024
      scale:
        min: 1
        max: 5
        policy:
          cpu:
            max: 70
      env:
        DATABASE_HOST: "${resource:mongodb.host}"
        DATABASE_NAME: "${resource:mongodb.database}"
        DATABASE_USER: "${resource:mongodb.user}"
      secrets:
        DATABASE_PASSWORD: "${resource:mongodb.password}"

Advanced Configuration: AWS CloudExtras

For advanced AWS ECS Fargate deployments, you can use cloudExtras to configure AWS-specific features:

stacks:
  staging:
    type: cloud-compose
    parent: myproject/devops
    config:
      # ... basic configuration above
      cloudExtras:
        # AWS IAM roles for the ECS task
        awsRoles:
          - "arn:aws:iam::123456789012:role/MyServiceRole"
          - "arn:aws:iam::123456789012:role/S3AccessRole"

        # Lambda scheduling for background tasks
        lambdaSchedule:
          expression: "rate(5 minutes)"
          timezone: "America/New_York"

        # Multiple Lambda schedules with request payloads
        lambdaSchedules:
          - name: "daily-report"
            expression: "cron(0 9 * * ? *)"  # Daily at 9 AM
            timezone: "UTC"
            request: |-
              {
                "requestId": "daily-report",
                "requestTime": "23/Jun/2024:09:00:00 +0000",
                "httpMethod": "POST",
                "path": "/api/reports/daily",
                "requestContext": {
                  "http": {
                    "path": "/api/reports/daily",
                    "method": "POST",
                    "protocol": "HTTP/1.1"
                  }
                },
                "body": "{\"type\":\"daily\",\"format\":\"pdf\"}",
                "headers": {
                  "Authorization": "Bearer ${secret:api-key}",
                  "Content-Type": "application/json"
                }
              }
          - name: "hourly-cleanup"
            expression: "rate(1 hour)"       # Every hour
            timezone: "America/Los_Angeles"
            request: |-
              {
                "requestId": "hourly-cleanup",
                "httpMethod": "POST",
                "path": "/api/cleanup",
                "body": "{\"action\":\"cleanup\",\"maxAge\":3600}",
                "headers": {
                  "Authorization": "Bearer ${secret:cleanup-api-key}"
                }
              }

        # Lambda routing and invoke configuration
        lambdaRoutingType: "weighted"  # or "simple"
        lambdaInvokeMode: "async"      # or "sync"

        # Security group configuration
        securityGroup:
          ingress:
            allowOnlyCloudflare: true    # Restrict to Cloudflare IPs
            cidrBlocks:                  # Additional CIDR blocks
              - "10.0.0.0/8"
              - "192.168.0.0/16"
            ipv6CidrBlocks:              # IPv6 CIDR blocks
              - "2001:db8::/32"

        # Load balancer type
        loadBalancerType: "alb"  # Application Load Balancer (default)
        # loadBalancerType: "nlb"  # Network Load Balancer

CloudExtras Field Reference

Field Type Description Example
awsRoles []string IAM roles to attach to ECS tasks ["arn:aws:iam::123:role/MyRole"]
lambdaSchedule object Single Lambda schedule configuration {expression: "rate(5 minutes)"}
lambdaSchedules []object Multiple Lambda schedules with request payloads [{name: "daily", expression: "cron(0 9 * * ? *)", request: "..."}]
lambdaRoutingType string Lambda routing strategy "weighted" or "simple"
lambdaInvokeMode string Lambda invocation mode "async" or "sync"
securityGroup object Security group ingress rules See security group configuration
loadBalancerType string Load balancer type "alb" or "nlb"

LambdaSchedules Object Fields: | Field | Type | Description | Required | |--------------|----------|--------------------------------------|----------| | name | string | Unique identifier for the schedule | Yes | | expression | string | Cron or rate expression | Yes | | timezone | string | Timezone for execution | Optional | | request | string | Complete HTTP request payload (JSON) | Yes |

Security Group Configuration

securityGroup:
  ingress:
    allowOnlyCloudflare: true      # Automatically allow Cloudflare IP ranges
    cidrBlocks:                    # Custom IPv4 CIDR blocks
      - "10.0.0.0/8"              # Private network
      - "203.0.113.0/24"          # Specific public range
    ipv6CidrBlocks:                # Custom IPv6 CIDR blocks
      - "2001:db8::/32"           # IPv6 range

Lambda Schedule Examples

# Single schedule (simple)
lambdaSchedule:
  expression: "rate(5 minutes)"
  timezone: "America/New_York"

# Multiple schedules with full request payloads
lambdaSchedules:
  - name: "every-minute-health-check"
    expression: "cron(* * * * ? *)"    # Every minute
    timezone: "UTC"
    request: |-
      {
        "requestId": "health-check",
        "requestTime": "23/Jun/2024:15:48:12 +0000",
        "httpMethod": "POST",
        "path": "/api/health",
        "requestContext": {
          "http": {
            "path": "/api/health",
            "method": "POST",
            "protocol": "HTTP/1.1"
          }
        },
        "body": "{\"check\":\"all\"}",
        "headers": {
          "Authorization": "Bearer ${secret:health-check-api-key}",
          "Content-Type": "application/json"
        }
      }
  - name: "monthly-billing"
    expression: "cron(0 0 1 * ? *)"    # First day of every month
    timezone: "America/New_York"
    request: |-
      {
        "requestId": "monthly-billing",
        "httpMethod": "POST",
        "path": "/api/billing/process",
        "body": "{\"period\":\"monthly\",\"notify\":true}",
        "headers": {
          "Authorization": "Bearer ${secret:billing-api-key}"
        }
      }

Lambda Schedule Request Structure:

  • name: Unique identifier for the schedule
  • expression: Cron or rate expression for timing
  • timezone: Timezone for schedule execution (optional)
  • request: Complete HTTP request payload sent to your Lambda function
  • requestId: Unique request identifier
  • requestTime: Timestamp of the request
  • httpMethod: HTTP method (GET, POST, etc.)
  • path: API endpoint path
  • requestContext: Additional request context
  • body: JSON request body (as string)
  • headers: HTTP headers including authentication

Step 2: Deploy the Service

sc deploy -s myservice -e staging

The service is automatically deployed to ECS Fargate using the defined settings.


Summary

Step Command Purpose
Define Secrets secrets.yaml Stores AWS & MongoDB Atlas credentials
Configure Infra server.yaml Defines ECS Fargate & MongoDB Atlas resources
Provision Infra sc provision -s devops Deploys AWS infrastructure
Define Service client.yaml Describes a microservice deployment
Deploy Service sc deploy -s myservice -e staging Deploys a microservice to ECS Fargate