diff --git a/.gitignore b/.gitignore index e5e4be9b5..8933ae269 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,5 @@ yarn.lock **/llms.txt **/llms-full.txt + +.claude/settings.local.json diff --git a/docs/jumpstart/action-items.mdx b/docs/jumpstart/action-items.mdx index c82a7e1d2..b1326a4ff 100644 --- a/docs/jumpstart/action-items.mdx +++ b/docs/jumpstart/action-items.mdx @@ -203,23 +203,6 @@ If your engagement with Cloud Posse includes Release Engineering, we will also n - - ### PATs for ECS with `ecspresso` - - - Create one fine-grained PAT with the following permission. - Please see [Creating a fine-grained personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) - - This PAT needs read access to your `infrastructure` repository: - - ```diff - Repository - + Contents: Read-only - + Metadata: Read-only - ``` - - Save the new fine-grained PAT as a GitHub environment secret in the new `example-app` private repository in your Organization. - - - ### PATs for EKS with ArgoCD diff --git a/docs/layers/software-delivery/ecs-atmos/ecs-atmos.mdx b/docs/layers/software-delivery/ecs-atmos/ecs-atmos.mdx new file mode 100644 index 000000000..86373fb4e --- /dev/null +++ b/docs/layers/software-delivery/ecs-atmos/ecs-atmos.mdx @@ -0,0 +1,560 @@ +--- +title: "ECS with Atmos" +sidebar_label: "ECS with Atmos" +sidebar_position: 5 +--- +import Intro from '@site/src/components/Intro'; +import KeyPoints from '@site/src/components/KeyPoints'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CollapsibleText from '@site/src/components/CollapsibleText'; + + + Deploy containerized applications to AWS ECS Fargate using [Atmos](https://atmos.tools) for configuration orchestration and [OpenTofu](https://opentofu.org) 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. + + + +- 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 + + +```mermaid +--- +title: Atmos ECS Deployment Lifecycle +--- +sequenceDiagram + actor dev as Developer + + participant commit as Application + + box GitHub Action Workflow + participant ci as CI + participant deploy as CD + end + + box ECS + participant ecr as ECR + participant service as ECS Service + participant cluster as ECS Cluster + end + activate cluster + + dev ->>+ commit: Create Commit + commit ->>+ ci: Trigger + ci ->> ecr: Build & Push Image + ci ->>+ deploy: Trigger + deactivate ci + deactivate commit + + deploy ->>+ service: atmos terraform deploy + + loop + deploy --> commit: Wait
Deployment Status + end + + service ->>+ cluster: Update Tasks + deactivate service + + loop + cluster ->> cluster: Remove old tasks + cluster ->> cluster: Add new tasks + end + deactivate cluster +``` + +## 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 + +:::tip Latest Examples + +Check out our [example app-on-ecs-v2](https://github.com/cloudposse-examples/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. + + + ```yaml title=".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 + ``` + + + + + The preview cleanup workflow destroys preview environments when pull requests are closed or when the `deploy` label is removed. + + + ```yaml title=".github/workflows/preview-cleanup.yml" + name: Preview Cleanup + on: + pull_request: + branches: ["main"] + types: [closed, unlabeled] + + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + + permissions: + id-token: write + contents: read + + jobs: + cleanup: + runs-on: ["ubuntu-latest"] + if: ${{ github.event.pull_request.state == 'closed' || !contains(github.event.pull_request.labels.*.name, 'deploy') }} + 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: Destroy Preview Environment + shell: bash + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + ATMOS_CLI_CONFIG_PATH: .github + run: | + atmos terraform destroy app -s preview -auto-approve + ``` + + + + + The main branch workflow builds a Docker image, deploys to dev, and creates a draft release for promotion. + + + ```yaml title=".github/workflows/main-branch.yaml" + name: Main Branch + on: + push: + branches: [main] + + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + + permissions: + id-token: write + contents: write + + 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 Public 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] + environment: + name: dev + 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 }}" + ATMOS_CLI_CONFIG_PATH: .github + run: | + atmos terraform deploy app -s dev + URL=$(atmos terraform output app -s dev --skip-init -- -raw url) + echo "url=$URL" >> $GITHUB_OUTPUT + + release: + runs-on: [ubuntu-latest] + needs: [deploy] + steps: + - name: Create/Update Draft release + uses: release-drafter/release-drafter@v6 + with: + publish: false + prerelease: false + config-name: configs/draft-release.yml + commitish: ${{ github.sha }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ``` + + + + + The release workflow promotes the Docker image tag and deploys to staging and then production with manual approval. + + + ```yaml title=".github/workflows/release.yaml" + name: Release + on: + release: + types: [published] + + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + + permissions: + id-token: write + contents: read + + jobs: + promote: + 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 Public ECR + uses: docker/login-action@v3 + with: + registry: ${{ vars.ECR_REGISTRY }} + + - uses: cloudposse/github-action-docker-promote@v0.5.0 + id: promote + with: + organization: cloudposse-examples + repository: app-on-ecs-v2 + registry: ${{ vars.ECR_REGISTRY }} + from: sha-${{ github.sha }} + to: ${{ github.event.release.tag_name }} + use_metadata: false + + outputs: + image: ${{ steps.promote.outputs.image }} + tag: ${{ steps.promote.outputs.tag }} + + deploy-staging: + needs: [promote] + runs-on: [ubuntu-latest] + name: deploy / staging + environment: + name: staging + 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.promote.outputs.image }}:${{ needs.promote.outputs.tag }}" + ATMOS_CLI_CONFIG_PATH: .github + run: | + atmos terraform deploy app -s staging + URL=$(atmos terraform output app -s staging --skip-init -- -raw url) + echo "url=$URL" >> $GITHUB_OUTPUT + + deploy-production: + needs: [deploy-staging, promote] + runs-on: [ubuntu-latest] + name: deploy / production + environment: + name: prod + 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.promote.outputs.image }}:${{ needs.promote.outputs.tag }}" + ATMOS_CLI_CONFIG_PATH: .github + run: | + atmos terraform deploy app -s prod + URL=$(atmos terraform output app -s prod --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: + +```yaml title="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: + +```bash +# Build and run on http://localhost:8080 +atmos up + +# Stop the application +atmos down +``` + +Run deployments locally using Atmos: + +```bash +# 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 + +- [app-on-ecs-v2](https://github.com/cloudposse-examples/app-on-ecs-v2): Example application repository +- [Atmos](https://atmos.tools): Configuration orchestration tool +- [OpenTofu](https://opentofu.org): Infrastructure-as-code tool +- [github-action-docker-build-push](https://github.com/cloudposse/github-action-docker-build-push): Docker build action +- [github-action-docker-promote](https://github.com/cloudposse/github-action-docker-promote): Docker image promotion action diff --git a/docs/layers/software-delivery/ecs-atmos/setup.mdx b/docs/layers/software-delivery/ecs-atmos/setup.mdx new file mode 100644 index 000000000..4b5dd25a5 --- /dev/null +++ b/docs/layers/software-delivery/ecs-atmos/setup.mdx @@ -0,0 +1,354 @@ +--- +title: "Setup ECS with Atmos" +sidebar_label: "Setup" +sidebar_position: 10 +description: "Setup guide for deploying containerized applications to ECS with Atmos and OpenTofu" +--- +import Intro from '@site/src/components/Intro'; +import KeyPoints from '@site/src/components/KeyPoints'; +import Steps from '@site/src/components/Steps' +import Step from '@site/src/components/Step' +import StepNumber from '@site/src/components/StepNumber' +import Note from '@site/src/components/Note' +import TaskList from '@site/src/components/TaskList' +import CollapsibleText from '@site/src/components/CollapsibleText'; +import Admonition from '@theme/Admonition' +import CodeBlock from '@theme/CodeBlock'; +import ApplicationIAMRole from '@site/examples/snippets/stacks/catalog/iam-role/example-app.yaml'; + + + This setup guide walks you through deploying containerized applications to AWS ECS Fargate using Atmos for configuration orchestration and OpenTofu for infrastructure-as-code. The setup uses a self-contained approach where application infrastructure definitions and workflows reside in the application repository, while shared infrastructure is managed in the infra repository. + + +## Overview + +| Step | Action | Repository | +| ---- | ------ | ---------- | +| 1. Verify platform infrastructure | Confirm VPC, ECS cluster, GitHub OIDC Provider, TFstate bucket app are deployed | `infra` | +| 2. Create application repository | Create repo from [cloudposse-examples/app-on-ecs-v2](https://github.com/cloudposse-examples/app-on-ecs-v2) template | GitHub | +| 3. Provision ECR registry | Add ECR image repository for the app | `infra` | +| 4. Provision IAM roles | Create IAM roles for the app to assume | `infra` | +| 5. Update tfstate-bucket-app access | Grant IAM roles access to state bucket | `infra` | +| 6. Update profiles/github | Configure GitHub OIDC roles in app repo | `app` | +| 7. Configure GitHub repository | Add `ECR_REGISTRY` variable | GitHub UI | +| 8. Provision shared dependencies | Deploy app dependencies (databases, queues, etc.) | `infra` | +| 9. Configure dependencies | Set up remote state for infra components | `app` | +| 10. Configure container definitions | Define ECS task and container settings | `app` | +| 11. Test configuration | Run `atmos terraform plan app -s preview` | `app` | + + + + ### Verify Platform Infrastructure + + Before deploying an ECS application, verify that the following platform infrastructure components are already deployed in your target AWS accounts: + + + - [ ] VPC deployed in each environment (`vpc` component) + - [ ] ECS cluster deployed in each environment (`ecs/cluster` component) + - [ ] GitHub OIDC Provider deployed in each environment (`github-oidc-provider` component) + - [ ] TFstate bucket app deployed in each environment (`tfstate-bucket-app` component) + + + + These components are typically deployed once and shared across multiple applications. See the [ECS](/layers/ecs/) documentation for detailed setup instructions. + + + + + ### Create Application Repository + + Create a new repository from the [cloudposse-examples/app-on-ecs-v2](https://github.com/cloudposse-examples/app-on-ecs-v2) template. + + + 1. Navigate to [cloudposse-examples/app-on-ecs-v2](https://github.com/cloudposse-examples/app-on-ecs-v2) + 2. Click "Use this template" → "Create a new repository" + 3. Choose a name for the repository (e.g., `acme/example-app`) + 4. Select "Private" for repository visibility + 5. Click "Create repository" + + + The template includes: + + - [ ] Sample Go application with Dockerfile + - [ ] GitHub Actions workflows for CI/CD + - [ ] Terraform/OpenTofu components for ECS task definitions + - [ ] Atmos stack configurations for each environment + + + + + ### Provision ECR Registry + + Add an ECR image repository for the new application. + + In your `infra` repository, update the ECR component configuration: + + ```yaml title="stacks/catalog/ecr.yaml" + components: + terraform: + ecr: + vars: + images: + - acme/example-app + # Additional configuration as needed + ``` + + ```bash + atmos terraform deploy ecr -s core-use1-artifacts + ``` + + + + ### Provision IAM Roles + + Create IAM roles that the application will assume for deployments. + + + {ApplicationIAMRole} + + + + ```bash + atmos terraform deploy example-app/iam-role -s plat-gbl-dev + atmos terraform deploy example-app/iam-role -s plat-gbl-staging + atmos terraform deploy example-app/iam-role -s plat-gbl-prod + ``` + + + + ### Update tfstate-bucket-app Access + + Grant the newly created IAM roles access to the Terraform state bucket. + + Add the IAM role ARN references to your tfstate backend configuration: + + ```yaml title="stacks/catalog/tfstate-backend/apps.yaml" + components: + terraform: + tfstate-backend-apps: + vars: + privileged_principal_arns: + - !terraform.state example-app/iam-role .role_arn + ``` + + ```bash + atmos terraform deploy tfstate-backend-apps -s plat-gbl-dev + atmos terraform deploy tfstate-backend-apps -s plat-gbl-staging + atmos terraform deploy tfstate-backend-apps -s plat-gbl-prod + ``` + + + + ### Update profiles/github + + Configure the app repository with the IAM roles provisioned in the previous steps. + + In your **application repository**, update the Atmos auth configuration with the provisioned IAM roles: + + ```yaml title="profiles/github/atmos.yaml" + # ... + + auth: + providers: + github-oidc: + kind: github/oidc + region: us-west-1 + spec: + audience: sts.amazonaws.com + + identities: + plat-dev/terraform: + kind: aws/assume-role + via: + provider: github-oidc + principal: + assume_role: arn:aws:iam::111111111111:role/acme-plat-gbl-dev-example-app-terraform + + plat-staging/terraform: + kind: aws/assume-role + via: + provider: github-oidc + principal: + assume_role: arn:aws:iam::222222222222:role/acme-plat-gbl-dev-example-app-terraform + + plat-prod/terraform: + kind: aws/assume-role + via: + provider: github-oidc + principal: + assume_role: arn:aws:iam::333333333333:role/acme-plat-gbl-dev-example-app-terraform + ``` + + + Get the role ARNs from the outputs of the IAM role deployments in step 5: + ```bash + atmos terraform output example-app/iam-role -s plat-gbl-dev -- -raw role_arn + ``` + + + + + ### Configure GitHub Repository + + Add repository `ECR_REGISTRY` variable required by the GitHub Actions workflows. + + + 1. Navigate to your repository → Settings → Secrets and variables → Actions + 2. Click the "Variables" tab + 3. Click "New repository variable" + 4. Add the following variable: + + | Name | Value | + |------|-------| + | `ECR_REGISTRY` | Your ECR registry URL (e.g., `444444444444.dkr.ecr.us-east-1.amazonaws.com`) | + + + + If you have multiple application repositories, consider creating an organization-level variable for `ECR_REGISTRY` to avoid repetition. + + + + + ### Provision Shared Dependencies + + Deploy application dependencies that have a different lifecycle than the application itself (e.g., databases, message queues, caches). + + + Skip this step if your application doesn't require external dependencies like databases or caches. + + + + + ### Configure Dependencies + + Your application needs to reference infrastructure that was already deployed by your infrastructure repository — specifically the VPC and ECS cluster. These dependency files tell Atmos where to find the Terraform backend for those resources, allowing your application stacks to look up output values like VPC IDs, subnet IDs, and cluster ARNs. + + :::tip + These files don't deploy any infrastructure. They define how to read the remote state of existing infrastructure components. + ::: + + In your **application repository**, create dependency stack files for each infrastructure component you need to reference: + + ```yaml title="terraform/stacks/deps/vpc.yaml" + components: + terraform: + vpc: + metadata: + component: vpc + type: abstract + terraform_workspace: "{{ .vars.tenant }}-{{ .vars.environment }}-{{ .vars.deps_stage }}" + backend_type: s3 + backend: + s3: + bucket: "acme-core-gbl-root-tfstate" + region: "us-east-1" + encrypt: true + key: terraform.tfstate + acl: bucket-owner-full-control + assume_role: + role_arn: "arn:aws:iam::111111111111:role/acme-core-gbl-root-tfstate-ro" + ``` + + ```yaml title="terraform/stacks/deps/ecs-cluster.yaml" + components: + terraform: + vpc: + metadata: + component: vpc + type: abstract + terraform_workspace: "{{ .vars.tenant }}-{{ .vars.environment }}-{{ .vars.deps_stage }}" + backend_type: s3 + backend: + s3: + bucket: "acme-core-gbl-root-tfstate" + region: "us-east-1" + encrypt: true + key: terraform.tfstate + acl: bucket-owner-full-control + assume_role: + role_arn: "arn:aws:iam::111111111111:role/acme-core-gbl-root-tfstate-ro" + ``` + + And any other external dependencies like databases or caches. + + + + ### Configure Container Definitions + + Define the ECS task and container settings for your application. + + ```yaml title="terraform/stacks/default/app.yaml" + components: + terraform: + app: + vars: + desired_count: 1 + cpu: 256 + memory: 512 + container_definitions: + app: + image: !env APP_IMAGE + port_mappings: + - containerPort: 8080 + hostPort: 8080 + protocol: "tcp" + environment: + - name: "APP_ENV" + value: "production" + secrets: + - name: "DATABASE_URL" + valueFrom: !terraform.state rds .database_url + ``` + + And/or update Terraform component (if needed). Modify the ECS task component in `terraform/components/ecs-task/` to match your application requirements: + + 1. Add additional IAM permissions + 2. Configure health check settings + 3. Set up log configuration + 4. Add sidecar containers + + + + + ### Test Configuration + + Verify the Atmos configuration works correctly. + + ```bash + atmos terraform plan app -s preview + ``` + + Check that: + + - [ ] All remote state references resolve correctly + - [ ] Container definitions are valid + - [ ] IAM roles and permissions are configured + - [ ] No errors in the plan output + + + + +## Quick Checklist + + +- [ ] Platform infrastructure verified (VPC, ECS cluster, GitHub OIDC Provider) +- [ ] `tfstate-bucket-apps` provisioned in dev/staging/prod +- [ ] Application repository created from template +- [ ] ECR registry provisioned (`core-use1-artifacts`) +- [ ] IAM roles provisioned in dev/staging/prod +- [ ] `tfstate-backend-apps` updated with IAM role access +- [ ] `profiles/github/atmos.yaml` configured with IAM role ARNs +- [ ] GitHub repo variable `ECR_REGISTRY` configured +- [ ] Shared dependencies provisioned (if applicable) +- [ ] Dependencies configured in `terraform/stacks/deps/` +- [ ] Container definitions configured in `terraform/stacks/default/app.yaml` +- [ ] `atmos terraform plan app -s preview` runs successfully + + +## References + +- [app-on-ecs-v2](https://github.com/cloudposse-examples/app-on-ecs-v2): Example application repository +- [Atmos](https://atmos.tools): Configuration orchestration tool +- [OpenTofu](https://opentofu.org): Infrastructure-as-code tool +- [GitHub OIDC with AWS](/layers/github-actions/github-oidc-with-aws): How to configure GitHub OIDC +- [github-action-docker-build-push](https://github.com/cloudposse/github-action-docker-build-push): Docker build action diff --git a/docs/layers/software-delivery/ecs-atmos/triggering-workflows.mdx b/docs/layers/software-delivery/ecs-atmos/triggering-workflows.mdx new file mode 100644 index 000000000..f22026187 --- /dev/null +++ b/docs/layers/software-delivery/ecs-atmos/triggering-workflows.mdx @@ -0,0 +1,58 @@ +--- +title: "Triggering Workflows" +sidebar_label: "Triggering Workflows" +sidebar_position: 20 +description: "How to trigger CI/CD workflows for ECS deployments" +--- +import Intro from '@site/src/components/Intro'; +import Steps from '@site/src/components/Steps' +import Step from '@site/src/components/Step' +import StepNumber from '@site/src/components/StepNumber' + + + Once the ECS application setup is complete, validate the CI/CD workflows by triggering deployments through pull requests and releases. + + + + + ### Create a Pull Request + + Create a PR and add the `deploy` label to deploy a preview environment: + + ```bash + git checkout -b feature/test-preview + # Make changes + git push origin feature/test-preview + ``` + + Then add the `deploy` label to the PR in GitHub. + + + + ### Merge PR into Main Branch + + Push any change to the `main` branch to trigger the main branch workflow: + + ```bash + git add . + git commit -m "Initial setup" + git push origin main + ``` + + This will: + - Build and push a Docker image to ECR + - Deploy to the `dev` environment + - Create a draft release + + + + + ### Publish a Release + + Edit the draft release created by the main branch workflow and click "Publish release". This will: + - Promote the Docker image tag + - Deploy to `staging` + - Wait for approval (if configured) + - Deploy to `prod` + + diff --git a/docs/layers/software-delivery/ecs-ecspresso/ecs-ecspresso.mdx b/docs/layers/software-delivery/ecs-ecspresso/ecs-ecspresso.mdx index 21a112b14..dad75ce0d 100644 --- a/docs/layers/software-delivery/ecs-ecspresso/ecs-ecspresso.mdx +++ b/docs/layers/software-delivery/ecs-ecspresso/ecs-ecspresso.mdx @@ -1,6 +1,6 @@ --- -title: "ECS with ecspresso" -sidebar_label: "ECS with ecspresso" +title: "ECS with ecspresso (Deprecated)" +sidebar_label: "ECS with ecspresso (Deprecated)" sidebar_position: 10 --- import Intro from '@site/src/components/Intro'; @@ -8,11 +8,15 @@ import KeyPoints from '@site/src/components/KeyPoints'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CollapsibleText from '@site/src/components/CollapsibleText'; +import Admonition from '@theme/Admonition'; We use the [`ecspresso`](https://github.com/kayac/ecspresso) deployment tool for Amazon ECS to manage ECS services using a code-driven approach, alongside reusable GitHub Action workflows. This setup allows tasks to be defined with Terraform within the infrastructure repository, and task definitions to reside alongside the application code. Ecspresso provides extensive configuration options via YAML, JSON, and Jsonnet, and includes plugins for enhanced functionality such as Terraform state lookups. + + This approach is deprecated. For new ECS deployments, we recommend using [ECS with Atmos](/layers/software-delivery/ecs-atmos/) which provides a simpler, more integrated deployment workflow using Atmos for configuration orchestration. + ```mermaid --- diff --git a/docs/layers/software-delivery/ecs-ecspresso/ecs-partial-task-definitions.mdx b/docs/layers/software-delivery/ecs-ecspresso/ecs-partial-task-definitions.mdx index f15fe60a0..f7f9cddda 100644 --- a/docs/layers/software-delivery/ecs-ecspresso/ecs-partial-task-definitions.mdx +++ b/docs/layers/software-delivery/ecs-ecspresso/ecs-partial-task-definitions.mdx @@ -6,11 +6,16 @@ sidebar_position: 20 import Intro from '@site/src/components/Intro'; import Steps from '@site/src/components/Steps'; +import Admonition from '@theme/Admonition' This document describes what partial task definitions are and how we can use them to set up ECS services using Terraform and GitHub Actions. + + This approach is deprecated. For new ECS deployments, we recommend using [ECS with Atmos](/layers/software-delivery/ecs-atmos/) which provides a simpler, more integrated deployment workflow. + + ## The Problem Managing ECS Services is challenging. Ideally, we want our services to be managed by Terraform so everything is living diff --git a/docs/layers/software-delivery/ecs-ecspresso/setup.mdx b/docs/layers/software-delivery/ecs-ecspresso/setup.mdx index c5a92c2a7..adaa20256 100644 --- a/docs/layers/software-delivery/ecs-ecspresso/setup.mdx +++ b/docs/layers/software-delivery/ecs-ecspresso/setup.mdx @@ -16,6 +16,10 @@ import AtmosWorkflow from '@site/src/components/AtmosWorkflow'; This setup guide will help you get started with [ecspresso](https://github.com/kayac/ecspresso). It features an example app, which demonstrates how your GitHub Actions work with your infrastructure repository. + + This approach is deprecated. For new ECS deployments, we recommend using [ECS with Atmos](/layers/software-delivery/ecs-atmos/) which provides a simpler, more integrated deployment workflow. + + | Steps | Actions | | ---------------------------------------------------- | --------------------------------------------------------------------------------- | | 1. Create a repository from the Example App template | [cloudposse-examples/app-on-ecs](https://github.com/cloudposse-examples/app-on-ecs) | diff --git a/docs/layers/software-delivery/fundamentals.mdx b/docs/layers/software-delivery/fundamentals.mdx index 900804c97..5b41fc303 100644 --- a/docs/layers/software-delivery/fundamentals.mdx +++ b/docs/layers/software-delivery/fundamentals.mdx @@ -137,7 +137,8 @@ manual confirmation. Once manually approved, the workflow will continue and depl Refer to our stack specific implementations for more details: - [**Dockerized App on EKS with ArgoCD**](/layers/software-delivery/eks-argocd/) -- [**Dockerized App on ECS with Ecspresso**](/layers/software-delivery/ecs-ecspresso/) +- [**Dockerized App on ECS with Atmos**](/layers/software-delivery/ecs-atmos/) +- [**Dockerized App on ECS with Ecspresso**](/layers/software-delivery/ecs-ecspresso/) (Deprecated) - [**Lambda App**](/layers/software-delivery/lambda) ## FAQ diff --git a/docs/layers/software-delivery/software-delivery.mdx b/docs/layers/software-delivery/software-delivery.mdx index 3592a7132..e114a8521 100644 --- a/docs/layers/software-delivery/software-delivery.mdx +++ b/docs/layers/software-delivery/software-delivery.mdx @@ -1,5 +1,6 @@ --- title: Software Delivery +sidebar_position: 1 --- import Intro from '@site/src/components/Intro'; import Steps from '@site/src/components/Steps'; @@ -30,10 +31,10 @@ import ReactPlayer from 'react-player'; Choose a path for delivery of your services with GitHub Actions. The reference architecture supports deployment to AWS EKS, Amazon ECS, and Lambda functions. - - We use the `ecspresso` deployment tool for Amazon ECS to manage ECS services using a code-driven approach, alongside reusable GitHub Action workflows. This setup allows tasks to be defined with Terraform within the infrastructure repository, and task definitions to reside alongside the application code. + + 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. - Get Started + Get Started Argo CD is an open-source declarative, GitOps continuous delivery tool for Kubernetes applications. It enables developers to manage and deploy applications on Kubernetes clusters using Git repositories as the source of truth for configuration and definitions. Our Argo CD implementation follows the GitOps methodology and integrates with GitHub Actions, ensuring that the entire application configuration, including manifests, parameters, and even application state, is stored in a Git repository. @@ -41,10 +42,20 @@ import ReactPlayer from 'react-player'; Get Started - + Deploy Lambda functions using GitHub Workflows with a code-driven approach. The build process updates S3 with assets and SSM with the new version, requiring a Terraform run for promotion. GitHub Workflows manage the entire lifecycle, from building and packaging Lambda functions to deploying them, with reusable workflows. Get Started + + + :::warning + This approach is deprecated. For new ECS deployments, we recommend using [ECS with Atmos](/layers/software-delivery/ecs-atmos/). + ::: + + We use the `ecspresso` deployment tool for Amazon ECS to manage ECS services using a code-driven approach, alongside reusable GitHub Action workflows. This setup allows tasks to be defined with Terraform within the infrastructure repository, and task definitions to reside alongside the application code. + + View Documentation +
@@ -60,7 +71,51 @@ Once you're done deploying your apps, it's time to start monitoring everything.

Our Examples

-### Reusable Workflows +### ECS with Atmos (Recommended) + +The [app-on-ecs-v2](https://github.com/cloudposse-examples/app-on-ecs-v2) example demonstrates a self-contained approach where workflows are defined directly in the application repository. This approach uses Atmos for configuration orchestration and OpenTofu for infrastructure management. + +With this approach, you need only a few workflows in your application repository: + + 1. `feature-branch.yml` - Build and deploy to preview environments + 2. `preview-cleanup.yml` - Clean up preview environments when PRs close + 3. `main-branch.yaml` - Build, deploy to dev, and create draft release + 4. `release.yaml` - Promote and deploy to staging/production + 5. (optional) `validate.yml` - Run validation checks + 6. (optional) `labeler.yaml` - Auto-label PRs + +```console +app-on-ecs-v2/ +├── .github/ +│ └── workflows/ +│ ├── feature-branch.yml +│ ├── preview-cleanup.yml +│ ├── main-branch.yaml +│ ├── release.yaml +│ ├── validate.yml +│ └── labeler.yaml +├── app/ +│ ├── main.go +│ └── Dockerfile +├── terraform/ +│ ├── components/ +│ │ └── ecs-task/ +│ └── stacks/ +│ ├── dev.yaml +│ ├── staging.yaml +│ ├── prod.yaml +│ └── preview.yaml +└── .opentofu-version +``` + +### ECS with Ecspresso (Deprecated) + +:::warning +The ecspresso-based approach is deprecated. For new projects, use [ECS with Atmos](/layers/software-delivery/ecs-atmos/) instead. +::: + +
+View deprecated ecspresso workflow structure We've consolidated all the workflows into the example applications, including the GitHub reusable workflows. @@ -141,3 +196,5 @@ After moving to a centralized workflow repository, you should have a setup like │ └── workflow-controller-hotfix-release.yml └── ... ``` + +
diff --git a/examples/snippets/stacks/catalog/iam-role/example-app.yaml b/examples/snippets/stacks/catalog/iam-role/example-app.yaml new file mode 100644 index 000000000..534e2e56a --- /dev/null +++ b/examples/snippets/stacks/catalog/iam-role/example-app.yaml @@ -0,0 +1,62 @@ + import: + - catalog/iam-role/defaults + + components: + terraform: + example-app/iam-role: + metadata: + component: iam-role + inherits: + - iam-role/defaults + vars: + name: example-app + attributes: + - "terraform" + role_description: | + Role for GitHub Actions to access the GitOps resources, such as the S3 Bucket and DynamoDB Table. + github_oidc_provider_enabled: true + github_oidc_provider_arn: !terraform.state github-oidc-provider oidc_provider_arn + trusted_github_org: acme + trusted_github_repos: + - example-app + policy_statements: + AllowECRAccess: + effect: "Allow" + actions: + - "ecr:GetAuthorizationToken" + - "ecr:BatchCheckLayerAvailability" + - "ecr:GetDownloadUrlForLayer" + - "ecr:BatchGetImage" + - "ecr:InitiateLayerUpload" + - "ecr:UploadLayerPart" + - "ecr:CompleteLayerUpload" + - "ecr:PutImage" + - "ecr:CreatePullThroughCacheRule" + - "ecr:BatchImportUpstreamImage" + - "ecr:CreateRepository" + resources: + - "*" + AllowAssumeRole: + effect: "Allow" + actions: + - "sts:AssumeRole" + - "sts:TagSession" + - "sts:SetSourceIdentity" + resources: + - "*" + AllowServicesAccess: + effect: "Allow" + actions: + - "s3:*" + - "iam:*" + - "ecs:*" + - "ecr:*" + - "logs:*" + - "ssm:*" + - "ec2:*" + - "elasticloadbalancing:*" + - "application-autoscaling:*" + - "cloudwatch:*" + - "kms:Decrypt" + resources: + - "*"