Skip to main content
Latest Documentation
This is the latest documentation for the Cloud Posse Reference Architecture. To determine which version you're currently using, please see Version Identification.

ECS with Atmos

Deploy containerized applications to AWS ECS Fargate using Atmos for configuration orchestration and OpenTofu for infrastructure-as-code. This approach provides a self-contained, elegant solution with automated CI/CD pipelines that leverage Atmos stack configurations for multi-environment deployments.

You will learn

  • Deploy Docker containers to ECS Fargate with infrastructure defined in Terraform/OpenTofu
  • Use Atmos stacks for environment-specific configurations (dev, staging, prod, preview)
  • Automated CI/CD with GitHub Actions using Atmos for both authentication and deployment
  • Preview environments for pull requests with automatic cleanup
  • Release promotion workflow from dev through staging to production

Overview

This approach uses Atmos to orchestrate the deployment of ECS services through Terraform/OpenTofu components. Each environment (dev, staging, prod, preview) is defined as an Atmos stack, allowing consistent configuration management across all environments.

Key benefits:

  • Simplified configuration: Atmos stacks provide a single source of truth for environment-specific settings
  • Native AWS authentication: Use atmos auth exec for secure credential management
  • Infrastructure as Code: ECS task definitions and services are managed through Terraform components
  • GitOps workflow: Configuration changes flow through Git with automated deployments
Latest Examples

Check out our example app-on-ecs-v2 for the latest example of how to deploy ECS applications with Atmos and GitHub Actions.

GitHub Action Workflows

The deployment process uses four main workflows that cover the complete development lifecycle.

The feature branch workflow builds a Docker image and deploys to a preview environment when the deploy label is added to a pull request.

.github/workflows/feature-branch.yml
name: Feature Branch
on:
pull_request:
branches: ["main"]
types: [opened, synchronize, reopened, labeled]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

permissions:
id-token: write
contents: read

jobs:
build:
runs-on: ["ubuntu-latest"]
steps:
- name: Install Atmos
uses: cloudposse/github-action-setup-atmos@v2
with:
install-wrapper: false

- name: Checkout
uses: actions/checkout@v4

- name: ECR password
shell: bash
run: |
atmos auth exec --identity plat-dev/admin -- aws configure export-credentials --format env-no-export >> $GITHUB_ENV
env:
ATMOS_CLI_CONFIG_PATH: .github

- name: Mask ECR credentials
shell: bash
run: |
echo "::add-mask::${{ env.AWS_SECRET_ACCESS_KEY }}"
echo "::add-mask::${{ env.AWS_ACCESS_KEY_ID }}"
echo "::add-mask::${{ env.AWS_SESSION_TOKEN }}"

- name: Login to ECR
uses: docker/login-action@v3
with:
registry: ${{ vars.ECR_REGISTRY }}

- name: Build
id: build
uses: cloudposse/github-action-docker-build-push@v2
with:
organization: cloudposse-examples
repository: app-on-ecs-v2
registry: ${{ vars.ECR_REGISTRY }}
workdir: app

outputs:
image: ${{ steps.build.outputs.image }}
tag: ${{ steps.build.outputs.tag }}

deploy:
needs: [build]
runs-on: ["ubuntu-latest"]
if: ${{ contains(github.event.pull_request.labels.*.name, 'deploy') }}
environment:
name: preview
url: ${{ steps.deploy.outputs.url }}
steps:
- name: Install Atmos
uses: cloudposse/github-action-setup-atmos@v2
with:
install-wrapper: false

- name: Checkout
uses: actions/checkout@v4

- uses: opentofu/setup-opentofu@v1
with:
tofu_wrapper: false
tofu_version_file: .opentofu-version

- name: Deploy ECS Service
shell: bash
id: deploy
env:
APP_IMAGE: "${{ needs.build.outputs.image }}:${{ needs.build.outputs.tag }}"
PR_NUMBER: ${{ github.event.pull_request.number }}
ATMOS_CLI_CONFIG_PATH: .github
run: |
atmos terraform deploy app -s preview
URL=$(atmos terraform output app -s preview --skip-init -- -raw url)
echo "url=$URL" >> $GITHUB_OUTPUT

Repository Structure

The example repository follows this structure:

app-on-ecs-v2/
├── .github/
│ └── workflows/ # CI/CD workflow definitions
│ ├── feature-branch.yml
│ ├── preview-cleanup.yml
│ ├── main-branch.yaml
│ ├── release.yaml
│ ├── labeler.yaml
│ └── validate.yml
├── app/ # Application source code
│ ├── main.go
│ └── Dockerfile
├── terraform/
│ ├── components/ # Terraform/OpenTofu components
│ │ └── ecs-task/ # ECS task definition component
│ └── stacks/ # Atmos stack configurations
│ ├── dev.yaml
│ ├── staging.yaml
│ ├── prod.yaml
│ └── preview.yaml
└── .opentofu-version # OpenTofu version pinning

Atmos Stack Configuration

Each environment is defined as an Atmos stack with environment-specific variables:

terraform/stacks/dev.yaml
import:
- catalog/defaults

vars:
environment: dev

components:
terraform:
app:
vars:
desired_count: 1
cpu: 256
memory: 512

Local Development

Run the application locally using Podman Compose:

# Build and run on http://localhost:8080
atmos up

# Stop the application
atmos down

Run deployments locally using Atmos:

# Deploy to dev environment
atmos terraform deploy app -s dev

# Deploy to staging
atmos terraform deploy app -s staging

# Deploy to production
atmos terraform deploy app -s prod

# View outputs
atmos terraform output app -s dev

References