Why I Gave Up on AWS Copilot CLI for Preview Environments

Tadashi Shigeoka ·  Tue, February 24, 2026

I wanted to automatically build an isolated preview environment for each pull request. The first candidate that came to mind was AWS Copilot CLI, AWS’s official ECS deployment tool. Copilot CLI is appealing for its simplicity—you can deploy an application to ECS Fargate with a single copilot svc deploy command—and the official documentation provides environment management mechanisms.

The bottom line: in my case, Copilot CLI was not a good fit. After a full day of PoC work, I couldn’t even get the application to start, and I concluded that Copilot CLI’s abstractions were more of a hindrance than a help.

This article documents what I tried, where I got stuck, and why I gave up. I hope it serves as a useful reference for anyone considering Copilot CLI.

PoC Goals

I created a new AWS account and aimed to perform the following end-to-end using only AWS Copilot CLI:

  • copilot app init to initialize the application
  • copilot svc init to initialize the API service (Load Balanced Web Service) and Worker service (background jobs)
  • copilot env init + copilot env deploy to set up the environment
  • copilot svc deploy to deploy (including Aurora Serverless v2 Addon)
  • Verify application startup, DB migration, and authentication integration

The goal was to quickly determine whether “Copilot CLI can run this app” before investing in manifest refinement or GitHub Actions automation. Cognito integration was the biggest risk, so I wanted to validate it early.

PoC Results

TaskResult
Application initialization (app init)Success
Service initialization (svc init)Success
Environment setup (env init + env deploy)Success
Service deployment (svc deploy)Failed—secrets/env var injection issues prevented app startup
DB migrationNot attempted (blocked by above)
Cognito integrationNot attempted
Full resource cleanup (app delete)Success

Environment setup went smoothly, but at the service deployment stage, secrets/environment variable injection became a wall, and the application never started.

Issues Discovered

Issue 1: Secrets/Environment Variable Injection (The Biggest Blocker)

Copilot CLI does not automatically grant the ECS Execution Role read permissions for SSM Parameter Store or Secrets Manager. I tried every approach below, and all failed:

MethodResult
secrets + SSM parameter pathssm:GetParameters AccessDenied
secrets + secretsmanager: prefixsecretsmanager:GetSecretValue AccessDenied
from_cfn to reference Addon output (secrets)No export named ... found
from_cfn to reference Addon output (variables)Same as above

The fundamental problem is that there are extremely limited ways to pass connection information from Addon-created resources (such as Aurora) to containers. There is also no way to add policies to the Execution Role from an Addon.

Issue 2: Addon Constraints

Copilot CLI’s Addon feature adds CloudFormation templates, but it came with the following constraints:

YAML syntax constraints: Copilot CLI’s parser does not work well with CloudFormation shorthand notation like !Sub and !Ref. Everything must be written in long form—Fn::Sub, Ref, etc. This is a particularly frustrating gotcha for anyone experienced with CloudFormation.

from_cfn doesn’t work: This is the official feature for referencing Addon outputs from the Manifest, but internally it uses Fn::ImportValue (which requires Exports), making it impossible to reference nested stack outputs. Since Addons are managed as nested stacks within Copilot CLI, there is a fundamental mismatch.

Issue 3: Compatibility with Real-World Complexity

Copilot CLI is designed for “simple apps that follow conventions.” When confronted with real-world complexity, here’s where the conflicts arose:

Project CharacteristicConflict with Copilot CLI
docker-entrypoint.sh depends on JSON-formatted DB secretsCannot be injected via Copilot CLI’s secrets mechanism
ManageMasterUserPassword Secret format doesn’t match app expectationsEven custom Secrets can’t be passed to the container
Multiple services (API / Admin / Worker) + DB + RedisManaging dependencies and resource sharing between Addons is complex
Schema management via DB migration toolFaces the same DB connection information passing problem

Issue 4: Slow Debug Loop

Trial and error with Copilot CLI was extremely time-consuming:

StepDuration
Aurora Serverless v2 creation15–20 min
Stack deletion10–15 min
One trial cycle30+ min

The following issues made debugging even harder:

  • When a service is removed due to ROLLBACK_COMPLETE, copilot svc logs --previous becomes unavailable
  • Nested stack (Addon) errors are inaccessible after deletion
  • You end up having to check CloudFormation event logs directly, negating the benefits of Copilot CLI’s abstractions

Issue 5: Customization Barriers

Once you try to go beyond Copilot CLI’s abstractions, you need CloudFormation knowledge anyway:

  • Customizing the Execution Role → No way to do it
  • Fine-grained control over Task Definitions (entrypoint override, etc.) → Requires taskdef_overrides
  • Sharing resources between Addons (e.g., API and Worker sharing the same Aurora) → Requires custom workarounds
  • Integrating with existing Secrets management patterns → Doesn’t fit Copilot CLI’s conventions

Overall Assessment

AspectEvaluation
Deploying simple appsCopilot CLI is excellent
Deploying complex appsNot a good fit
Learning & debugging costCloudFormation knowledge is required anyway; the abstraction becomes a barrier

Notably, bypassing docker-entrypoint.sh required entrypoint overriding via taskdef_overrides. On the other hand, copilot app delete for full resource cleanup worked without issues—that’s a point in its favor.

Copilot CLI is a great tool for “deploying a Hello World-level app to ECS as fast as possible.” However, once real-world requirements like DB connections, Secrets management, and multi-service coordination come into play, the abstraction layer becomes a constraint.

The most critical issue was that there is effectively no way to inject connection information from Addon-created resources into containers. I believe this is a problem that anyone would face when trying to run a production-level application with Copilot CLI, not just for preview environments.

Direction After Giving Up

Deploying to ECS Fargate itself is a valid approach. The problem was IaC tool selection. Based on lessons learned from the Copilot CLI PoC, I evaluated the following alternatives:

OptionAdvantagesConcerns
TerraformMature HCL ecosystem, rich module libraryDynamic per-PR environment management tends to be verbose
AWS CDKCan be written in TypeScript, superset of CloudFormationIndirectly subject to CloudFormation constraints
PulumiTypeScript, Review Stacks feature, Stack ReferencePulumi Cloud costs

Summary

AWS Copilot CLI is an appealing tool for its simplicity, but watch out if your project involves:

  • Passing connection information from Addon-created resources to containers (DB, Redis, etc.)
  • Customizing the Execution Role (access to SSM, Secrets Manager, etc.)
  • Sharing resources across multiple services (e.g., API and Worker using the same DB)
  • Long debug trial cycles (Aurora creation time + stack deletion time)

The PoC approach of “let’s first see if Copilot CLI works” was the right call. Being able to reach a “not a good fit” conclusion in a single day allowed me to smoothly pivot to Pulumi. If you’re unsure about tool selection, I recommend running a small PoC to make an early decision.

That’s all for today—when it comes to IaC tool selection, try small and decide fast. That’s all from the Gemba.