NAME

App::FargateStack

SYNOPSIS

# Dry-run and analyze the configuration
app-FargateStack plan -c my-stack.yml

# Provision the full stack
app-FargateStack apply -c my-stack.yml

DESCRIPTION

App::FargateStack is a lightweight deployment framework for Amazon ECS on Fargate. It enables you to define and launch containerized services with minimal AWS-specific knowledge and virtually no boilerplate. Designed to simplify cloud infrastructure without sacrificing flexibility, the framework lets you declaratively specify tasks, IAM roles, log groups, secrets, and networking in a concise YAML configuration.

By automating the orchestration of ALBs, security groups, EFS mounts, CloudWatch logs, and scheduled or daemon tasks, App::FargateStack reduces the friction of getting secure, production-grade workloads running in AWS. You supply a config file, and the tool intelligently discovers or provisions required resources.

It supports common service types such as HTTP, HTTPS, daemon, and cron tasks, and handles resource scoping, role-based access, and health checks behind the scenes. It assumes a reasonable AWS account layout and defaults, but gives you escape hatches where needed.

App::FargateStack is ideal for developers who want the power of ECS and Fargate without diving into the deep end of Terraform, CloudFormation, or the AWS Console.

Current Status of App::FargateStack

This is a work in progress. Versions prior to 1.1.0 are considered usable but may still contain issues related to edge cases or uncommon configuration combinations.

This documentation corresponds to version 1.0.43.

The release of version 1.1.0 will mark the first production-ready release. Until then, you're encouraged to try it out and provide feedback. Issues or feature requests can be submitted via GitHub.

Caveats

  • The documentation may be incomplete or inaccurate.

  • Features may change, and new ones will be added. See the "ROADMAP" for details.

  • Deploying resources using this framework may result in AWS charges.

  • This software is provided "as is", without warranty of any kind. Use at your own risk.

Features

  • Minimal configuration: launch a Fargate service with just a task name and container image

  • Supports multiple task types: HTTP, HTTPS, daemon, cron (scheduled)

  • Automatic resource provisioning: IAM roles, log groups, target groups, listeners, etc.

  • Discovers and reuses existing AWS resources when available (e.g., VPCs, subnets, ALBs)

  • Secret injection from AWS Secrets Manager

  • CloudWatch log integration with configurable retention

  • Optional EFS volume support (per-task configuration)

  • Public or private service deployment (via ALB in public subnet or internal-only)

  • Built-in service health check integration

  • Automatic IAM role and policy generation based on task needs

  • Optional HTTPS support with ACM certificate discovery and creation

  • Optional support for adding AWS WAF support for your HTTPS site

  • Lightweight dependency stack: Perl, AWS CLI, a few CPAN modules

  • Convenient CLI: start, stop, update, and tail logs for any service

METHODS AND SUBROUTINES

This class is implemented as a modulino and is not designed for traditional object-oriented use. As such, this section is intentionally omitted.

USAGE

Commands

Command                   Arguments                    Description
-------                   ---------                    -----------
add-scaling-policy        See Note 12                  adds an autoscaling policy to the configuration
add-schedule-action       See Note 13                  adds a scheduled scaling action
apply                                                  reads config and creates resources
create-stack              app-name service-clauses...  creates a new stack configuration
delete-scaling-policy     task-name                    deletes the autoscaling policy for a task from your configuration
delete-scheduled-action   action-name                  deletes a named scheduled action from your configuration
delete-service            task-name                    alias for remove-service
delete-task               task-name                    deletes all resources associated with a task (See Note 11)
delete-autoscaling-policy task-name                    deletes a metric based scaling policy for the task
delete-scheduled-action   action-name                  deletes an existing autoscaling scheduled action
delete-scheduled-task     task-name                    deletes all resources associated with a scheduled task (See Note 11)
delete-daemon             task-name                    deletes all resources associated with a daemon  (See Note 11)
delete-http-service       task-name                    deletes all resources associated with a http service  (See Note 11)
deploy-service            task-name                    create a new service (see Note 4)
disable-scheduled-task    task-name                    disable a scheduled task
enable-scheduled-task t   ask-name                     enable a scheduled task
help                      [subject]                    displays general help or help on a particular subject (see Note 2)
list-tasks                                             list running or stopped tasks
list-zones                domain                       list the hosted zones for a domain
logs                      task-name start end          display CloudWatch logs (see Note 5)
plan                                                  reads config and reports on resource creation
register-task-definition  task-name                    creates a new task definition revision
remove-service            task-name                    removes an existing service but does not delete the task
run-task                  task-name                    launches an adhoc task
show                      command args                 output additional info about the stack or run states
 cloudtrail-events task-name start-time [end-time]  show cloudtrail events for a scheduled task (useful for debugging)
 stack                                                shows a summary of the stack configuration
start-service             task-name [count]            starts a service
status                    task-name                    provides the current status for a task
stop-service              task-name                    stops a running service
update-policy                                         updates the ECS policy in the event of resource changes
update-target             task-name                    force update of target definition
version                                               display the current version number

Options

-h, --help                 help
--cache, --no-cache        use the configuration file as the source of truth (see Note 8)
-c, --config               path to the .yml configuration
-C, --create-alb           forces creation of a new ALB, prevents use of an existing ALB
--color, --no-color        default: color
--confirm-all              confirm deletion of all resources
-d, --dryrun               just report actions, do not apply
--dns-profile              alias for --route53-profile
-f, --force                force action (depends on context)
--history, --no-history    save cli parameters to .fargatestack/defaults.json
--log-level                'trace', 'debug', 'info', 'warn', 'error', default: info (See Note 6)
--log-time, --no-log-time  for logs command, output CloudWatch timestamp (default: --no-log-time)
--log-wait, --no-log-wait  for logs command, continue to monitor logs (default: --log-wait)
--log-poll-time            amount of time in seconds to sleep between requesting new log events
--max-events, -m           maximum number of events to show for status command (default: 5)
--output                   output type for some commands, valid values: text|json
-p, --profile              AWS profile (see Note 1)
--purge-config             remove deleted tasks from multi-task configs
--route53-profile          set this if your Route 53 zones are in a different account (See Note 10)
-s, --skip-register        skips registering a new task definition when using update-target (See Note 7)
-u, --update, --no-update  update config (See Note 9)
-U, --unlink, --no-unlink  delete or keep temp files (default: --unlink)
-w, --wait, --no-wait      wait for tasks to complete and then dump the log (applies to adhoc tasks)
-v, --version              script version

Notes

(1) Use the --profile option to override the profile defined in the configuration file.

Note: The Route 53 service uses the same profile unless you specify --route53-profile or set a profile name in the route53 section of the configuration file.

(2) You can get help using the --help option or use the help command with a subject or one of the commands.
app-FargateStack help overview
app-FargateStack help redeploy

If you do not provide a subject then you will get the same information as --help. Use help help to get a list of available subjects.

(3) You must log at least at the 'info' level to report progress. This is set for you when your plan or apply.
(4) By default an ECS service is NOT created for you by default for daemon and http tasks. Instead, after creating all of the necessary resources using apply, run app-FargateStack deploy-service task-name. This will launch your service with a count of 1 task. You can optionally specify a different count after the task name.
(5) You can tail or display a set of log events from a task's log stream:
app-Fargate logs [--log-wait] [--log-time] start end
--log-wait --no-log-wait (optional)

Continue to monitor stream and dump logs to STDOUT

default: --log-wait

--log-time, --no-log-time (optional)

Output the CloudWatch timestamp of the message.

default: --log-time

task-name

The name of the task whose logs you want to view.

start

Starting date and optionally time of the log events to display. Format can be one of:

Nd => N days ago
Nm => N minutes ago
Nh => N hours ago

mm/dd/yyyy
mm/dd/yyyy hh:mm::ss
end

If provided both start and end must date-time strings.

(6) The default log level is 'info' which will create an audit trail of resource provisioning. Certain commands log at the 'error' level to reduce console noise. Logging at lower levels will prevent potential useful messages from being displayed. To see the AWS CLI commands being executed, log at the 'debug' level. The 'trace' level will output the result of the AWS CLI commands.
(7) Use --skip-register if you want to update a tasks target rule without registering a new task definition. This is typically done if for some reason your target rule is out of sync with your task definition version.
(8) To speed up processing and avoid unnecessary API calls the framework considers the configuration file the source of truth and a reliable representation of the state of the stack. If you want to re-sync the configuration file set --no-cache and run plan. In most cases this should not be necessary as the framework will invalidate the configuration if an error occurs forcing a re-sync on the next run of plan or apply.
(9) --no-update is not permitted with apply. If you need a dry plan without applying or updating the config, use --dryrun (and optionally --no-update) with plan.
(10) Set --route53-profile to the profile that has permissions to manage your hosted zones. By default the script will use the default profile.
(11) Deleting a task, daemon, or http service will delete all of the resources associated with that task.
  • For scheduled tasks you can disable the job from running instead of deleting its resources.

  • For services (daemons or HTTP services) you can stop them or delete the service (delete-service) instead of deleting all of the resources.

  • These resources will NOT be removed:

    - ECR image associated with a task
    - An ACM certificate provisioned by App::FargateStack
(12) This command will add a scaling policy to an HTTP, HTTPS or daemon task. In order to apply the policy you must run plan & apply. You provide the following arguments in order:
[task-name] metric-type metric-value [min-capacity max-capacity [scale-out-cooldown scale-in-cooldown]]
  • task-name is optional if you only have 1 scalable task.

  • min-capacity, max-capacity are optional and will default to 1 and 2 respectively.

  • scale-out-cooldown, scale-in-cooldown are optional. If you provided you must include the capacity paramters.

    app-FargateStack apache requests 500 2 3 60 300
(13) This command will add a schedule scaling action to your configuration. In order to activate the schedule you must run plan and apply. You provide the following arguments in order:
[task-name] action-name start-time end-time days scale-out-capacity scale-in-capacity
  • task-name is optional if you only have 1 scalable task.

  • action-name is a name for your schedule. It must be unique within your entire configuration.

  • start-time is UTC. The format for the staring time is MM::HH. (Example: 00:18)

  • days is the day or days of the week for the scheduled action.

    Note: Days should be one of MON,TUE,WED,THU,FRI,SAT or 1-7

    Example:

    Scale out to 4 tasks at 10pm (EDT) for 30 minutes to run a batch job on Friday night.

    00:02 30:02 SAT 4/1 4/1

    Note that the cron specification is in UTC, hence we run at 2am for 30 minutes on Saturday morning in UTC.

  • end-time time t scale back in. Same format as start-time

  • scale-out-capacity, scale-in-capacity - These options represent the scale out and scale in capacities.

    Each value should be a tuple separated by '/', ',', ':' or '-'. The first value represents the minimum or maximum capacity for scaling out or in at the specified starting time of schedule action. The second value represents the minimum or maximum capacity for scaling in or out at the ending time of the action.

    Example to scale out to 2 tasks during business hours of 8:30am and 5:30pm and scale in to 1 task during non-business hours.

    app-FargateStack add-scheduled-action business_hours 30:12 30:21 MON-FRI 2/1 2/1

    If you had a scaling policy, your scaling policies max_capacity must be greater than or equal to the largest maximum capacity of your all of you scheduled actions for that task.

    app-FargateStack add-scheduled-action business_hours 30:12 30:21 2/1 4/1

    In this case, your scaling policy max_capacity value must be at least 4.

OVERVIEW

NOTE: This is a brief introduction to App::FargateStack. To see a list of topics providing more detail use the help help command.

The App::FargateStack framework, as its name implies provides developers with a tool to create Fargate tasks and services. It has been designed to make creating and launching Fargate based services as simple as possible. Accordingly, it provides logical and pragmatic defaults based on the common uses for Fargate based applications. You can however customize many of the resources being built by the script.

Using a YAML based configuration file, you specify your required resources and their attributes, run the app-FargateStack script and launch your application.

Using this framework you can:

  • ...build internal or external facing HTTP services that:

    • ...automatically provision certificates for external facing web applications

    • ...use an existing or create a new internal or external facing application load balancer (ALB).

    • ...automatically create an alias record in Route 53 for your domain

    • ...create a listener rule to redirect port 80 requests to 443

  • ...create queues and buckets to support your application

  • ...use a dryrun mode to report the resources that will be built before building them

  • ...run app-FargateStack multiple times (idempotency)

  • ...create daemon services

  • ...create scheduled jobs

  • ...execute adhoc jobs

Additional Features

inject secrets into the container's environment using a simple syntax (See "INJECTING SECRETS FROM SECRETS MANAGER")
detection and re-use of existing resources like EFS files systems, load balancers, buckets and queues
automatic IAM role and policy generation based on configured resources
define and launch multiple independent Fargate tasks and services under a single stack
automatic creation of log groups with customizable retention period
discovery of existing environment to intelligently populate configuration defaults
automatically create a minimal Fargate app/service config from shorthand

Minimal Configuration

Getting a Fargate task up and running requires that you provision and configure multiple AWS resources. Stitching it together using Terraform or CloudFormation can be tedious and time consuming, even if you know what resources to provision AND how to stitch it together.

The motivation behind writing this framework was to take the drudgery of writing declarative resource generators for all of the resources required to run a simple task, create basic web applications or RESTful APIs. Instead, we wanted a framework that covered 90% of our use cases while allowing our development workflow to go something like:

Create a Docker image that implements our worker, web app or API
Create a minimal configuration file that describes our application
Execute the framework's script and create the necessary AWS infrastructure
Launch the http server, daemon, scheduled job, or adhoc worker

Of course, this is only a "good idea" if creating the initial configuration file is truly minimal, otherwise it becomes an exercise similar to using Terraform or CloudFormation. So what is the minimum amount of configuration to inform our framework so it can create our Fargate worker? How's this for minimal?

---
app:
  name: my-stack
tasks:
  my-worker:
    type: task
    image: my-worker:latest
    schedule: cron(50 12 * * * *)

TIP: You can use the "create-stack" command to create minimal configuration files for various Fargate application scenarios.

Using this minimal configuration and running app-FargateStack like this:

app-FargateStack plan

...the framework would create the following resources in your VPC:

  • a cluster named my-stack-cluster

  • a security group for the cluster

  • an IAM role for the the cluster

  • an IAM policy that has permissions enabling your worker

  • an ECS task definition that describes your task

  • a CloudWatch log group

  • an EventBridge target event

  • an IAM role for EventBridge

  • an IAM policy for EventBridge

  • an EventBridge rule that schedules the worker

...so as you can see, rolling all of this by hand could be a daunting task and one made even more difficult when you decide to use other AWS resources inside your task like buckets, queues or an EFS file systems!

Web Applications

Creating a web application using a minimal configuration works too. To build a web application you can start with this minimal configuration:

---
app:
  name: my-web-app
domain: my-web-app.example.com
tasks:
  apache:
    type: https
    image: my-web-app:latest

This will create an externally facing web application for you with these resources:

  • A certificate for your domain

  • A Fargate cluster

  • IAM roles and policies

  • A listener and listener rules

  • A CloudWatch log group

  • Security groups

  • A target group

  • A task definition

  • An ALB if one is not detected

Once again, launching a Fargate service requires a lot of fiddling with AWS resources! Getting all of the plumbing installed and working requires a lot of what and how knowledge.

Adding or Changing Resources

Adding or updating resources for an existing application should also be easy. Updating the infrastructure should just be a matter of updating the configuration and re-running the framework's script. When you update the configuration the App::FargateStack will detect the changes and update the necessary resources.

Currently the framework supports adding a single SQS queue, a single S3 bucket, volumes using EFS mount points, environment variables and secrets from AWS Secrets Manager.

my-worker:
  image: my-worker:latest
  command: /usr/local/bin/my-worker.pl
  type: task
  schedule: cron(00 15 * * * *)   
  bucket:
    name: my-worker-bucket
  queue:
    name: my-worker-queue
  environment:
    ENVIRONMENT=prod
  secrets:
    db_password:DB_PASSWORD
  efs:
    id: fs-abcde12355
    path: /
    mount_point: /mnt/my-worker

Adding new resources would normally require you to update your policies to allow your worker to access these resource. However, the framework automatically detects that the policy needs to be updated when new resources are added (even secrets) and takes care of that for you.

See app-Fargate help configuration for more information about resources and options.

Configuration as State

The framework attempts to be as transparent as possible regarding what it is doing, how long it takes, what the result was and most importantly what defaults were used during resource provisioning. Every time the framework is run, the configuration file is updated based on any new resources provisioned or configured. For example, if you did not specify subnets, they are inferred by inspecting your VPC and automatically added to the configuration file.

This gives you a single view into your Fargate application

CLI OPTION DEFAULTS

When enabled, App::FargateStack automatically remembers the most recently used values for several CLI options between runs. This feature helps streamline repetitive workflows by eliminating the need to re-specify common arguments such as the AWS profile, region, or config file.

The following options are tracked and persisted:

  • --profile

  • --region

  • --config

  • --route53-profile

  • --max-events

These values are stored in .fargatestack/defaults.json within your current project directory. If you omit any of these options on subsequent runs, the most recently used value will be reused.

Typically, you would create a dedicated project directory for your stack and place your configuration file there. Once you invoke a command that includes any of the tracked CLI options, the .fargatestack/defaults.json file will be created automatically. Future commands run from that directory can then omit those options. A typical workflow to create a new stack with a scheduled job might look like this:

mkdir my-project
cd my-project
app-FargateStack create-stack foo task:my-cron image:my-project 'schedule:cron(0 10 * * * *)'
app-FargateStack plan
app-FargateStack apply

That's it...you just created a scheduled job that will run at 10 AM every day!

Disabling and Resetting

Use the --no-history option to temporarily disable this feature for a single run. This allows you to override stored values without modifying or deleting them.

To clear all saved defaults entirely, use the reset-history command. This removes all of the tracked values from the .fargatestack/defaults.json file, but preserves the file itself.

Notes

Only explicitly provided CLI options are tracked. Values derived from environment variables or configuration files are not saved.

This feature is enabled by default.

COMMAND LIST

The basic syntax of the framework's CLI is:

app-FargateStack command --config fargate-stack.yml [options] command-args

You must provide at least a command.

Configuration File Naming

Your configuration file can be named anything, but by convention your configuration file should have a .yml extension. If you don't provide a configuration filename the default configuration file fargate-stack.yml will be used. You can also set the FARGATE_STACK_CONFIG environment variable to the name of your configuration file.

Command Logging

Commands will generally produce log output at the default level (info). You can see what AWS commands are being executed using the debug level. If you'd like see the results of the AWS CLI commands use the trace level.
Commands that are expected to produce informational output (e.g. status, logs, list-tasks, list-zone, etc) will log at the error level which will eliminate log noise on the console.
Logs are written to STDERR.
The default is to colorize log messages. Use --no-color if you don't like color.

Command Descriptions

help

help [subject]

Displays basic usage or help on a particular subject. To see a list of help subject use help help. The script will attemp to do a regexp match if you do provide the exact help topic, so you can cheat and use shortened versions of the topic.

help cloudwatch

add-autoscaling-policy

add-scaling-policy

This command will add a scaling policy to an HTTP, HTTPS or daemon task. In order to apply the policy you must run plan & apply. You provide the following arguments in order:

[task-name] metric-type metric-value [min-capacity max-capacity [scale-out-cooldown scale-in-cooldown]]

Example:

app-FargateStack add-scaling-policy cpu 60 1 3
task-name

The task in your configuration that will contain the new scaling policy. This is optional if you only have 1 scalable task.

metric-type (required)

One of cpu or requests

metric-value (required)

The metric value. For cpu it should be an integer between 1 and 100. For requests it should be a count representing the number of requests your ALB receives per minute.

min-capacity

The minimum number of tasks to maintain.

default: 1

max-capacity

The maximum number of tasks to scale up.

default: 2

scale-out-cooldown

The number of seconds to wait before scaling up another task.

default: 60

scale-in-cooldown

The number of seconds to wait until scaling down a task.

default: 300 (5 minutes)

add-scheduled-action

This command will add a schedule scaling action to your configuration. In order to activate the schedule you must run plan and apply. You provide the following arguments in order:

[task-name] action-name start-time end-time days scale-out-capacity scale-in-capacity
task-name (optional)

The task in your configuration that will contain the new scheduled action configuration. This is optional if you only have 1 scalable task.

action-name

action-name is a name for your schedule. It must be unique within your entire configuration.

start-time

The starting time of the scheduled action as MM::HH (UTC).

Example: 00:18
end-time

The time to scale back in. Same format as start-time.

days

The the day or days of the week for the scheduled action.

Note: Days should be one of MON,TUE,WED,THU,FRI,SAT or 1-7

Example 1:

Scale out to 4 tasks at 10pm (EDT) for 30 minutes to run a batch job on Friday night.

00:02 30:02 SAT 4/1 4/1

Note that the cron specification is in UTC, hence we run at 2am for 30 minutes on Saturday morning in UTC.

scale-out-capacity
scale-in-capacity

These options represent the scale out and scale in capacities.

Each value should be a tuple separated by '/', ',', ':' or '-'. The first value represents the minimum or maximum capacity for scaling out or in at the specified starting time of schedule action. The second value represents the minimum or maximum capacity for scaling in or out at the ending time of the action.

Example 1:

To scale out to 2 tasks during business hours of 8:30am and 5:30pm and scale in to 1 task during non-business hours (with no metric based scaling policy):

app-FargateStack add-scheduled-action business_hours 30:12 30:21 2/1 2/1

Note that without a scaling policy your minimum and maximum capacities for scaling in and out must be equal.

Example 2:

If your task includes a scaling policy, your scaling policy's max_capacity must be greater than or equal to the largest maximum capacity of your scheduled action.

app-FargateStack add-scheduled-action business_hours 30:12 30:21 2/1 3/1

In this case, your scaling policy max_capacity value must be at least 4. You autoscaling: section will look like this:

tasks:
  apache:
    type: https
    autoscaling:
      min_capacity: 1
      max_capacity: 3
      requests: 1000
      scale_in_cooldown: 300
      scale_out_cooldown: 60
      scheduled:
        business_hours:
          start_time: 30:12
          end_time: 21:30
          min_capacity: 2/1
          max_capacity: 3/1

Note:

Scheduled actions are only for HTTP, HTTPS and daemon tasks. If you need to run a one-shot job at a particular time use a scheduled task.

apply

Reads the configuration file and determines what actions to perform and what resources will be built. Builds resources incrementally and updates configuration file with resource details.

create-stack

create-stack app-name service-clauses...

Parses a compact, positional CLI grammar and emits a ready-to-edit YAML configuration for your Fargate framework. The command does not create any AWS resources; it only synthesizes config based on the clauses you pass.

Examples:

# One task service
app-fargate create-stack foo task:job image:myrepo:1.2.3

# HTTP service (ALB) + image
app-fargate create-stack foo http:web image:site:2025-08-14 domain:api.example.com

# HTTPS service (ALB + ACM; config only, no deploy)
app-fargate create-stack foo https:web image:site:stable domain:api.example.com

# Scheduled task (EventBridge schedule expression)
app-fargate create-stack foo scheduled:bar 'schedule:cron(0 10 * * * *)' image:helloworld

# Multiple services in one run
app-fargate create-stack foo \
  task:ingest image:etl:42 \
  scheduled:nightly 'schedule:rate(1 day)' image:etl:42 \
  http:api image:rest:latest domain:api.example.com

Service clause grammar

Each service is introduced by <type>:<name> followed by its required key:value pairs. You may specify multiple services in one command.

Valid type values and minimum keys:

environment
environment:RUN_ONCE=1

Sets an environment variable in the task. You can use env: as an abbreviation for environment:.

task
task:<name> image:<repo[:tag]>

Non-daemon task that can be run on demand.

http
http:<name> image:<repo[:tag]> domain:<fqdn>

ALB-backed HTTP service.

https
https:<name> image:<repo[:tag]> domain:<fqdn>

ALB-backed HTTPS service (certificate discovery/validation is out of scope for this command; see the env checker).

scheduled
scheduled:<name> image:<repo[:tag]> schedule:<expr>

EventBridge-scheduled task. schedule must be a valid cron(...) or rate(...) expression. Quote it in the shell, for example: 'schedule:cron(0 10 * * * *)'.

Note: You can use task: or scheduled: to indicate a scheduled task as long as you include a schedule: term.

daemon
daemon:<name>

Long-running service without a load balancer.

image
image:<repo[:tag]>

If image is given as repo[:tag] without a registry host:

  • The command assumes the image lives in the current account's ECR and will format the Docker reference as:

    <account_id>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>
  • If ECR lookup does not find the repository+tag, the tool emits a warning and leaves the image string as-is (allowing public registries like Docker Hub to work). This preserves convenience while making the fallback explicit.

  • Fully-qualified images (e.g., public.ecr.aws/namespace/image:tag, docker.io/library/nginx:1.27) are accepted as-is.

autoscaling
autoscaling:cpu|request[=value]

For services of type https, http, or daemon, you can enable and configure autoscaling directly from the command line. This provides a quick-start method to make your service elastic from the moment it's created.

The autoscaling: keyword accepts a metric and an optional target value:

  • Enable with a specific target value:

    autoscaling:requests=500
    autoscaling:cpu=60

    This will enable autoscaling and set the target for either ALB requests per task or average CPU utilization.

  • Enable with default target value:

    autoscaling:requests
    autoscaling:cpu

    If you omit the target value, a sensible default will be used (e.g., 500 for requests, 60 for CPU).

When the create-stack command sees the C>autoscaling:> keyword, it will generate a complete autoscaling block in your configuration file. This block will be populated with safe defaults (min_capacity: 1, max_capacity: 2), the specified metric, and all other necessary fields, making it easy to review and customize later. See "AUTOSCALING" for a full list of configuration options.

waf
waf:true|enabled|default|rule...

For https services, you can enable and configure an AWS Web Application Firewall (WAF) directly from the command line. This provides a powerful shortcut to bootstrapping a secure, production-ready WAF with minimal configuration.

The waf: keyword is highly flexible and accepts several forms:

  • Enable with defaults:

    waf:true
    waf:enabled
    waf:default

    Any of these will enable WAF and apply the default managed rule bundle, which provides a strong security baseline including protections against the OWASP Top 10 and SQL injection.

  • Enable with specific rule sets:

    You can specify a comma-separated list of rule set keywords. This allows you to tailor the protection to your application's specific needs from the very first command.

    waf:base,php,admin
  • Enable with bundles and subtractive syntax:

    For more complex configurations, you can use pre-configured bundles and the subtractive syntax (prefixing a keyword with a -) to remove unwanted rule sets.

    waf:all,-windows,-php

When the create-stack command sees the waf: keyword, it will automatically generate the corresponding waf block in your fargate-stack.yml file, including enabled: true and the specified managed_rules. See "Configuring Managed Rules" for a full list of available keywords and bundles.

For more information see "AWS WAF Support".

Output

Emits YAML to STDOUT that includes:

  • account, profile, region

  • app.name set from the first positional <app-name>

  • Optional domain (for HTTP/HTTPS stacks)

  • tasks map keyed by service <name> with fields such as type, image, and schedule (when applicable)

Options

--route53-profile STR

AWS profile to use when performing Route 53 API calls. Many environments use a separate account for DNS management; this option lets you target that account. If not provided, the tool uses --profile.

This option is only consulted when the command needs Route 53 (for example, HTTP/HTTPS stacks that require hosted zone lookups or record creation).

--dns-profile STR

Alias for --route53-profile.

--region STR

AWS region used when expanding ECR shorthand.

--out FILE

Write YAML to a file instead of STDOUT.

--force

Proceed even if some validations warn (for example, missing ECR repo).

Exit Status

0 on success
non-zero on argument or validation errors

NOTES

  • This command generates config; it does not deploy. Run your normal "plan/apply" flow after reviewing the YAML.

  • For HTTP/HTTPS, domain: is required at creation time in this shorthand.

  • Always quote schedule:... to avoid shell interpretation of parentheses.

deploy-service

deploy-service service-name

When you provision an HTTP, HTTPS, or daemon service, the framework sets up all the necessary infrastructure components -- but it does not automatically create and start the ECS service.

Use this command to start the service:

app-FargateTask deploy-service service-name

If you want to start multiple tasks for the service, you can include a count argument:

app-FargateTask deploy-service service-name 2

delete-daemon

delete-daemon task-name

Deletes the AWS resources associated with a task of type daemon. Consider removing the service ("remove-service") or stopping the service ("stop-service") if you do not want to delete the actual resources.

See "Notes on Deletion of Resources" for additional details.

delete-scheduled-task

delete-scheduled-task task-name

Deletes the AWS resources associated with a task of type task that includes a schedule: key.

See "Notes on Deletion of Resources" for additional details.

delete-task

delete-task task-name

Deletes the AWS resources associated with a task of type task.

See "Notes on Deletion of Resources" for additional details.

delete-http-service

Deletes the AWS resources associated with a task of type http or https.

If the Application Load Balancer (ALB) used by the service was provisioned by App::FargateStack, it will be automatically deleted. However, if the ALB was discovered but not created by App::FargateStack, it will be preserved. In that case, only the listener rules provisioned by App::FargateStack will be removed.

This command will also not delete any ACM certificate that was provisioned by App::FargateStack.

See "Notes on Deletion of Resources" for additional details.

disable-scheduled-task

disable-scheduled-task task-name

Use this command to disable a scheduled task.

If you omit task-name, the command will attempt to determine the target task selecting the task of type task with a defined schedule: key but only if exactly one such task is defined in your configuration file.

enable-scheduled-task

enable-scheduled-task task-name

Use this command to enable a scheduled task.

If you omit task-name, the command will attempt to determine the target task selecting the task of type task with a defined schedule: key but only if exactly one such task is defined in your configuration file.

list-tasks

list-tasks [stopped]

Lists running or stopped tasks and outputs a table of information about the tasks.

Task Name
Task Id
Status
Memory
CPU
Start Time
Elapsed Time
Stopped Reason

list-zones

list-zones domain-name

This command will list the hosted zones for a specific domain. The framework automatically detects the appropriate hosted zone for your domain if the zone_id: key is missing from your configuration when you have an HTTP or HTTPS task defined.

Example:

app-FargateStack list-zones --profile prod

logs

logs start-time end-time

To view your log streams use the logs command. This command will display the logs for the most recent log stream in the log group. By default the start time is the time of the first event.

Use --log-wait to continuously poll the log stream.
Use --no-log-time if your logs already have timestamps and do not want to see CloudWatch timestamps. This is useful when you are logging time in your time zone and do not want to be confused seeing times that don't line up.
start-time can be a "Nh", "Nm", "Nd" where N is an integer and h=hours ago, m=minutes ago and d=days ago.
start-time and end-time can be "mm/dd/yyyy hh:mm:ss" or just "mm/dd/yyyy"
end-time must always be a date-time string.

plan

Reads the configuration file and determines what actions to perform and what resources will be built. Only updates configuration file with resource details but DOES NOT build them.

redeploy

redeploy service-name

Forces a new deployment of the specified ECS service without registering a new task definition. This triggers ECS to stop the currently running task and launch a new one using the same task definition revision.

If you omit service-name, the command will attempt to determine the target service by selecting the task of type daemon, http, or https, but only if exactly one such service is defined in your configuration file.

If the task definition references an image by tag (such as :latest), this command ensures ECS re-pulls the image from ECR at deployment time. This allows you to deploy a newly pushed image without needing to create a new revision of the task definition.

This command is especially useful when:

  • You have pushed a new version of an image using the same tag (e.g. :latest)

  • You want to roll the service without changing other configuration

  • You want to confirm ECS tasks are using the most up-to-date image tag from ECR

Note that if your task definition references an image by digest (e.g. @sha256:...), ECS will continue to use that exact image. In that case, you must register a new task definition to update the image.

For best results, use this command as a shortcut to avoid register-task, update-service steps and only when your service's task definition uses an image tag that can be re-resolved, such as :latest or a CI-generated version tag.

register-task-definition

register-task-definition task-name

Creates a new task definition revision in ECS for the specified task.

Under normal circumstances, you should not need to run this command manually. Task definitions are automatically registered when you execute plan or apply.

This command is provided for exceptional cases where you need to force a new revision using a previously generated task definition file.

Warning: You should not manually modify the generated file (taskdef-{task-name}.json), as doing so may cause App::FargateStack to lose track of your task's configuration.

remove-service

remove-service service-name

Deletes a running ECS service without removing any of the underlying AWS resources.

If you simply want to stop the service temporarily, use the stop-service command instead.

This command does not delete associated infrastructure such as the target group, security group, or load balancer listener rules. To delete those resources, see "delete-daemon" or "delete-http-service", depending on the task type.

run-task

run-task task-name

Launches a one-shot Fargate task. By default, the command waits for the task to complete and streams the task's logs to STDERR. Use the --no-wait option to launch the task and return immediately.

When you register a task definition, ECS records the image digest of the image specified in your configuration file. If you later push a new image tagged with the same name (e.g., latest) without updating the task definition, ECS will continue to use the original image digest.

To detect this kind of drift, app-FargateStack records the image digest at the time of task registration and compares it to the current digest associated with the tag (typically latest) at runtime.

If the digests do not match, the default behavior is to abort execution and warn you about the mismatch. To override this safety check and proceed anyway, use the --force option.

state

state config-name

You can use this command to switch the default configuration that app-FargateStack will use when run without arguments.

The default configuration controls which task profile, region, and configuration file are considered "current." This allows you to run commands without repeatedly specifying the same options.

This command will output the table below that shows the currently active defaults:

.--------------------------------------------------------------------------------------------------.
|                                    Current Defaults: http-test                                   |
+---------+-------------+-----------+-------------------------------------------------+------------+
| Profile | DNS Profile | Region    | Config                                          | Max Events |
+---------+-------------+-----------+-------------------------------------------------+------------+
| sandbox | prod        | us-east-1 | /home/rlauer/git/App-FargateStack/http-test.yml |          5 |
'---------+-------------+-----------+-------------------------------------------------+------------'

status

status service-name

Displays the status of a running service and its most recent event messages in tabular form.

If you omit service-name, the command will attempt to determine the target service by selecting the task of type daemon, http, or https, but only if exactly one such service is defined in your configuration file.

Use the --max-events option to control how many recent events are shown. The default is 5.

stop-task

stop-task task-arn|task-id

Stops a running task. To get the task id, use the list-tasks command.

stop-service

stop-service service-name

Stops a running service by setting its desire count to 0.

If you omit service-name, the command will attempt to determine the target service by selecting the task of type daemon, http, or https, but only if exactly one such service is defined in your configuration file.

start-service

start-service service-name [count]

Start a service. count is the desired count of tasks. The default count is 1.

If you omit service-name, the command will attempt to determine the target service by selecting the task of type daemon, http, or https, but only if exactly one such service is defined in your configuration file.

update-policy

update-policy

Forces the framework to re-evaluate resources and align the policy. Will not apply changes in --dryrun mode. Under normal circumstances you should not need to run this command, however if you find that your Fargate policy lacks permissions for resources you have configure, this will make sure that all configured resources are included in your policy.

If update-policy identifies a need to update your role policy, you can view the changes before they are applied by running the plan command at the trace log level.

app-Fargate --log-level trace plan

update-service

update-service [service-name]

Updates an ECS service's configuration to use the latest registered task definition. This is the primary command for deploying any changes to your application, including new container images, environment variables, or resource allocations.

When an ECS service is launched, it is "pinned" to a specific revision of a task definition (e.g., my-task:9). If you later push a new container image or change the task's configuration in your fargate-stack.yml, the running service will not automatically pick up those changes.

This command is the essential final step in the deployment process.

  • If the service is running, this command will trigger a rolling deployment to replace the existing tasks with new ones based on the new task definition.

  • If the service is stopped, this command updates its configuration. The next time you run start-service, it will launch tasks using the new task definition.

When to Use update-service vs. redeploy While both commands can result in a new deployment, they serve different purposes:

Use update-service when you have made any changes to your configuration file that affect the task definition. This is the correct command for deploying a new image, adding environment variables, injecting secrets, changing CPU/memory, or adding EFS mount points. The workflow is:

Update your fargate-stack.yml file.

Run app-FargateStack register-task-definition task-name.

Run app-FargateStack update-service task-name.

Use redeploy as a shortcut only when you have pushed a new image using the same tag (e.g., :latest) and have made no other configuration changes. redeploy forces a new deployment using the existing task definition, which is simpler but will not apply any other updates.

The status command can help you detect drift by showing if the running task definition is out of sync with your latest configuration.

update-target

update-target task-name

Updates an EventBridge rule and rule target. For tasks of type "task" (typically scheduled jobs) when you change the schedule the rule must be deleted, re-created and associated with the target task. This command will detect the drift in your configuration and apply the changes if not in --dryrun mode.

version

Outputs the current version of App::FargateStack.

Notes on Deletion of Resources

  • You will be prompted to confirm the operation before any task is deleted.

  • If the specified task is the only one defined in your configuration file, its configuration will not be fully removed. Instead, the task's provisioned resource ARNs and names will be deleted, leaving behind a minimal configuration skeleton. This allows you to re-provision the task later by running plan against the skeleton, avoiding the need to recreate it from scratch.

  • App::FargateStack does not delete ECR images associated with tasks.

  • ACM certificates provisioned by App::FargateStack will not be deleted.

DEPLOYMENT WORKFLOW GUIDE

One of the most common questions when managing a stack is, "I changed X, what command(s) do I need to run now?" This guide provides a quick-reference matrix to help you choose the correct workflow for the most common changes.

How to Use This Matrix

Find the change you made in the "Change Description" column and follow the row across to see which commands are required. Commands should be run in order from left to right.

+---------------------------------------------+---------+---------+----------+----------+
| Change Description                          | apply   | register| update-  | redeploy |
|                                             |         | -task   | service  |          |
+---------------------------------------------+---------+---------+----------+----------+
| Updated container image (new tag/digest)    |         |    X    |    X     |          |
|---------------------------------------------+---------+---------+----------+----------|
| Updated container image (same :latest tag)  |         |         |          |    X     |
|---------------------------------------------+---------+---------+----------+----------|
| Added/changed environment variables         |         |    X    |    X     |          |
|---------------------------------------------+---------+---------+----------+----------|
| Added/changed secrets                       |    X    |    X    |    X     |          |
|---------------------------------------------+---------+---------+----------+----------|
| Added/changed CPU, memory, or size          |         |    X    |    X     |          |
|---------------------------------------------+---------+---------+----------+----------|
| Changed a scheduled task's cron/rate        |    X    |         |          |          |
|---------------------------------------------+---------+---------+----------+----------|
| Added a new S3 bucket or SQS queue          |    X    |    X    |    X     |          |
|---------------------------------------------+---------+---------+----------+----------|
| Added or changed an EFS mount point         |    X    |    X    |    X     |          |
+---------------------------------------------+---------+---------+----------+----------+

Notes on the Workflow

  • plan is Your Best Friend: Before running apply or any command that makes changes, it is always a good practice to run app-FargateStack plan first. This will give you a dry-run preview of the changes and help you catch any configuration errors.

  • Why apply is Sometimes Needed: Changes that affect AWS resources beyond the ECS task definition itself -- like IAM permissions for a new secret, EventBridge rules for a new schedule, or provisioning a new S3 bucket -- require running apply to create or update that infrastructure.

  • redeploy is a Shortcut: The redeploy command is a special case. It's a convenient shortcut for the common scenario where you've pushed a new image to the :latest tag and need to force a deployment without changing the task definition itself. For all other changes, the register-task and update-service workflow is the correct and safer path.

CLOUDWATCH LOG GROUPS

A CloudWatch log group is automatically provisioned for each application stack. By default, the log group name is /ecs/<application-name>, and log streams are created per task.

For example, given the following configuration:

app:
  name: my-stack
...
tasks:
  apache:
    type: https

The framework will:

  • ...create a log group named /ecs/my-stack

  • ...configure the apache task to write log streams with a prefix like my-stack/apache/*

By default, the log group is set to retain logs for 14 days if retention_days is not specified. You can override this by specifying a custom retention period using the retention_days key in the task's log_group section:

log_group:
  retention_days: 30

Log Group Notes

  • The log group is reused if it already exists.

  • Only numeric values accepted by CloudWatch are valid for retention_days (e.g., 1, 3, 5, 7, 14, 30, 60, 90, etc.).

  • You can customize the log group name by setting the name in the log_group: section (not recommended).

    log_group:
      retention_days: 14
      name: /ecs/my-stack
  • You can change the retention period by updating the configuration file and re-running apply.

  • To retain logs indefinitely, remove the retention_days entry in your configuration file.

IAM PERMISSIONS

This framework uses a single IAM role for all tasks defined within an application stack. The assumption is that services within the stack share a trust boundary and operate on shared infrastructure. This simplifies IAM management while maintaining strict isolation between stacks.

IAM roles and policies are automatically created based on your configuration. Only the minimum required permissions are granted. For example, if your configuration defines an S3 bucket, the ECS task role will be permitted to access only that specific bucket - not all buckets in your account. The policy is updated when new resources are added to the configuration file.

The role name an role policy name are found under the role: key in the configuration. A role name and role policy name are automatically fabricated for you from the name you specified under the app: key.

SECURITY GROUPS

A security group is automatically provisioned for your Fargate cluster. If you define a task of type http or https, the security group attached to your Application Load Balancer (ALB) is automatically authorized for ingress to your Fargate task. This is a rule allowing ALB-to-Fargate traffic.

FILESYSTEM SUPPORT

EFS volumes are defined per task and mounted according to the task definition. This design provides fine-grained control over EFS usage, rather than treating it as a global, stack-level resource.

Each task that requires EFS support must include both a volume and mountPoint configuration. The ECS task role is automatically updated to allow EFS access based on your specification.

To specify EFS support in a task:

efs:
  id: fs-1234567b
  mount_point: /mnt/my-stack
  path: /
  readonly:

Acceptable values for readonly are "true" and "false".

Field Descriptions

id:

The ID of an existing EFS filesystem. The framework does not provision the EFS, but will validate its existence in the current AWS account and region.

mount_point:

The container path to which the EFS volume will be mounted.

path:

The path on the EFS filesystem to map to your container's mount point.

readonly:

Optional. Set to true to mount the EFS as read-only. Defaults to false.

Additional Notes

  • The ECS role's policy for your task is automatically modified to allow read/write EFS access. Set readonly: in your task's efs: section to "true" if only want read support.

  • Your EFS security group must allow access from private subnets where the Fargate tasks are placed.

  • No changes are made to the EFS security group; the framework assumes access is already configured

  • Only one EFS volume is currently supported per task configuration.

  • EFS volumes are task-scoped and reused only where explicitly configured.

  • The framework does not automatically provision an EFS filesystem for you. The framework does however validate that the filesystem exists in the current account and region.

CONFIGURATION

The App::FargateStack framework defines your application stack using a YAML configuration file. This file describes your application's services, their resource needs, and how they should be deployed. Then configuration is updated whenever your run plan or apply.

GETTING STARTED

The fastest way to get up and running with App::FargateStack is to use the create-stack command to generate a configuration file, inspect the deployment plan, and then apply it.

Step 1: Create a Configuration Stub

First, generate a minimal YAML configuration file. The create-stack command provides a shorthand syntax to do this. You only need to provide an overall application name, a service type, a service name, and the container image to use.

This command will create a file named my-stack.yml in your current directory. Make sure you have your AWS profile configured in your environment or pass it using the --profile option.

app-FargateStack create-stack my-stack daemon:my-stack-daemon image:my-stack-daemon:latest

This will produce a configuration stub that looks like this:

app:
  name: my-stack
tasks:
  my-stack-daemon:
    image: my-stack-daemon:latest
    type: daemon

This file contains the three key pieces of information you provided: the application name, the task name, and the image to use.

Step 2: Plan the Deployment (Dry Run)

Next, run the plan command. This is a crucial step that acts as a dry run. The framework will:

  • Read your minimal configuration file.

  • Intelligently discover resources in your AWS account (like your VPC and subnets).

  • Determine what new resources need to be created (like IAM roles, a security group, an ECS cluster and a CloudWatch log group).

  • Report a full plan of action without making any actual changes.

  • Update your configuration file with the discovered values and sensible defaults.

app-FargateStack plan

After this command completes, your my-stack.yml file will be fully populated with all the information needed to provision your stack.

Step 3: Apply the Plan

Once you have reviewed the plan and are satisfied with the proposed changes, run the apply command. This will execute the plan and create all the necessary AWS resources.

app-FargateStack apply

Step 4: Deploy and Start the Service

The apply command creates all the necessary infrastructure, but it does not start your service. This separation allows you to manage your infrastructure and your application's runtime state independently.

To create the ECS service and start your container, use the deploy-service command.

app-FargateStack deploy-service my-stack-daemon

By default, this will start one instance of your task. To check its status, use the status command:

app-FargateStack status my-stack-daemon

And to stop the service, simply run:

app-FargateStack stop-service my-stack-daemon

To restart a stopped service, run:

app-FargateStack start-service my-stack-daemon

VPC AND SUBNET DISCOVERY

If you do not specify a vpc_id in your configuration, the framework will attempt to locate a usable VPC automatically.

A VPC is considered usable if it meets the following criteria:

  • It is attached to an Internet Gateway (IGW)

  • It has at least one available NAT Gateway

If no eligible VPCs are found, the process will fail with an error. If multiple eligible VPCs are found, the framework will abort and list the candidate VPC IDs. You must then explicitly set the vpc_id: in your configuration to resolve the ambiguity.

If exactly one eligible VPC is found, it will be used automatically, and a warning will be logged to indicate that the selection was inferred.

SUBNET SELECTION

If no subnets are specified in the configuration, the framework will query all subnets in the selected VPC and categorize them as either public or private.

The task will be placed in a private subnet by default. For this to succeed, your VPC must have at least one private subnet with a route to a NAT Gateway, or have appropriate VPC endpoints configured for ECR, S3, STS, CloudWatch Logs, and any other services your task needs.

If subnets are explicitly specified in your configuration, the framework will validate them and warn if they are not reachable or are not usable for Fargate tasks.

Task placement and Availability Zones

The framework places each task's ENI into exactly one subnet, which fixes that task in a single AZ. A service can span multiple AZs by listing subnets from at least two AZs.

What the framework does:

  • Prefers private subnets

    If private subnets are defined in the configuration, tasks are placed there. If no private subnets are defined, the framework falls back to public subnets.

  • Aligns ALB AZs with task placement

    When a load balancer is used, the framework enables the ALB in the same AZ set it selects for tasks (best practice). This is for resilience and to avoid unnecessary cross-AZ hops; it is not a hard technical requirement.

  • Requires two subnets

    The configuration must specify at least two subnets in different AZs. If subnets are not specified, the framework attempts to discover them, but still requires at least two usable subnets (either both private or both public). If fewer than two are available, it errors with guidance.

Notes on internet access and ALBs:

  • Internet-facing ALB

    An internet-facing ALB must be created in public subnets. Tasks may (and usually should) remain in private subnets behind it.

  • Egress from private subnets

    For image pulls and outbound calls, use either a NAT Gateway in each AZ or VPC endpoints for ECR (api and dkr) and S3.

  • Egress from public subnets

    If tasks are placed in public subnets without endpoints or NAT, they require assignPublicIp=ENABLED to reach ECR/S3.

REQUIRED SECTIONS

At minimum, your configuration must include the following:

app:
  name: my-stack

tasks:
  my-task:
    image: my-image
    type: daemon | task | http | https

For task types http or https, you must also specify a domain name:

domain: example.com

FULL SCHEMA OVERVIEW

The framework will expand and update your configuration file with default values as needed. Here is the full schema outline. All keys are optional unless otherwise noted:

---
account:
alb:
  arn:
  name:
  port:
  type:
app:
  name:             # required
  version:
certificate_arn:
cluster:
  arn:
  name:
default_log_group:
domain:              # required for http/https tasks
id:
last_updated:
region:
role:
  arn:
  name:
  policy_name:
route53:
  profile:
  zone_id:
security_groups:
  alb:
    group_id:
    group_name:
  fargate:
    group_id:
    group_name:
subnets:
  private:
  public:
tasks:
  my-task:
    arn:
    cpu:
    family:
    image:           # required
    log_group:
      arn:
      name:
      retention_days:
    memory:
    name:
    size:
    target_group_arn:
    target_group_name:
    task_definition_arn:
    type:            # required (daemon, task, http, https)
vpc_id:

TASK SIZE

To simplify task configuration, the framework supports a shorthand key called size that maps to common CPU and memory combinations supported by Fargate.

If specified, the size parameter should be one of the following profile names:

tiny     => 256 CPU, 512 MB memory
small    => 512 CPU, 1 GB memory
medium   => 1024 CPU, 2 GB memory
large    => 2048 CPU, 4 GB memory
xlarge   => 4096 CPU, 8 GB memory
2xlarge  => 8192 CPU, 16 GB memory

When a size is provided, the framework will automatically populate the corresponding cpu and memory values in the task definition. If you manually specify cpu or memory alongside size, those manual values will take precedence and override the defaults from the profile.

Important: If you change the size after an initial deployment, you should remove any manually defined cpu and memory keys in your configuration. This ensures that the framework can correctly apply the new profile values without conflict.

If neither size, cpu, nor memory are provided, the framework will infer a sensible default size based on the task type. For example:

- "http" or "https" => "medium"
- "task"            => "small"
- "task" + schedule => "medium"
- "daemon"          => "medium"

This behavior helps minimize configuration boilerplate while still providing sane defaults.

ENVIRONMENT VARIABLES

The Fargate stack framework allows you to define environment variables for each task. These variables are included in the ECS task definition and made available to your container at runtime.

Environment variables are specified under the environment: key within the task configuration.

BASIC USAGE

task:
  apache:
    environment:
      ENVIRONMENT: prod
      LOG_LEVEL: info
      DEBUG_MODE: 0

Each key/value pair will be passed to the container as an environment variable.

Environment variable values are treated literally; shell-style expressions such as ${VAR} are not interpolated. If you need dynamic values, populate them explicitly in the configuration or use the secrets: block for sensitive data.

This mechanism is ideal for non-sensitive configuration such as runtime flags, environment names, or log levels.

SECURITY NOTE

Avoid placing secrets (such as passwords, tokens, or private keys) directly in the environment: section. That mechanism is intended for non-sensitive configuration data.

To securely inject secrets into the task environment, use the secrets: section of your task configuration. This integrates with AWS Secrets Manager and ensures secrets are passed securely to your container.

INJECTING SECRETS FROM SECRETS MANAGER

To inject secrets into your ECS task from AWS Secrets Manager, define a secrets: block in the task configuration. Each entry in this list maps a Secrets Manager secret path to an environment variable name using the following format:

/secret/path:ENV_VAR_NAME

Example:

task:
  apache:
    secrets:
      - /my-stack/mysql-password:DB_PASSWORD

This configuration retrieves the secret value from /my-stack/mysql-password and injects it into the container environment as DB_PASSWORD.

Secrets are referenced via their ARN using ECS's native secrets mechanism, which securely injects them without placing plaintext values in the task definition.

BEST PRACTICES

Avoid placing secrets in the environment: block. That block is for non-sensitive configuration values and exposes data in plaintext.

Use clear, descriptive environment variable names (e.g., DB_PASSWORD, API_KEY) and organize your Secrets Manager paths consistently with your stack naming.

SQS QUEUES

The Fargate stack framework supports configuring and provisioning a single AWS SQS queue, including an optional dead letter queue (DLQs).

A queue is defined at the stack level and is accessible to all tasks and services within the same stack. IAM permissions are automatically scoped to include only the explicitly configured queue and its associated DLQ (if any).

Only one queue and one optional DLQ may be configured per stack.

BASIC CONFIGURATION

At minimum, a queue requires a name:

queue:
  name: fu-man-q

If you define max_receive_count in the queue configuration, a DLQ will be created automatically. You can optionally override its name and attributes using the top-level dlq key:

queue:
  name: fu-man-q
  max_receive_count: 5

dlq:
  name: custom-dlq-name

If you do not specify a dlq.name, the framework defaults to appending -dlq to the main queue name (e.g., fu-man-q-dlq).

DEFAULT QUEUE ATTRIBUTES

If not specified, the framework applies default values to match AWS's standard SQS behavior:

queue:
  name: fu-man-q
  visibility_timeout: 30
  delay_seconds: 0
  receive_message_wait_time_seconds: 0
  message_retention_period: 345600
  maximum_message_size: 262144
  max_receive_count: 5  # triggers DLQ creation

dlq:
  visibility_timeout: 30
  delay_seconds: 0
  receive_message_wait_time_seconds: 0
  message_retention_period: 345600
  maximum_message_size: 262144

DLQ DESIGN NOTE

A dead letter queue is not a special type - it is simply another queue used to receive messages that have been unsuccessfully processed. It is modeled as a standalone queue and defined at the top level of the stack configuration.

The dlq block is defined at the same level as queue, not nested within it. If no overrides are provided, DLQ attributes default to AWS attribute defaults.

IAM POLICY UPDATES

Adding a new queue to an existing stack will not only create the queue, but also update the IAM policy associated with your stack to include permissions for the newly defined queue and DLQ (if applicable).

SCHEDULED JOBS

The Fargate stack framework allows you to schedule container-based jobs using AWS EventBridge. This is useful for recurring tasks like report generation, batch processing, database maintenance, and other periodic workflows.

A scheduled job is defined like any other task, using type: task, and adding a schedule: key in AWS EventBridge cron format.

SCHEDULING A JOB

To schedule a job, add a schedule: key to your task definition. The value must be a valid AWS cron expression, such as:

cron(0 2 * * ? *)   # every day at 2:00 AM UTC

Example:

tasks:
  daily-report:
    type: task
    image: report-runner:latest
    schedule: cron(0 2 * * ? *)

Note: All cron expressions are interpreted in UTC.

The framework will automatically create an EventBridge rule tied to the task definition. When triggered, it will launch a one-off Fargate task based on the configuration. The EventBridge rule is named using the pattern "<task>-schedule".

All scheduled tasks support environment variables, secrets, and other standard task features.

RUNNING AN ADHOC JOB

You can run a scheduled (or unscheduled) task manually at any time using:

app-FargateStack run-task task-name

By default, this will:

  • Launch the task using the defined image and configuration

  • Wait for the task to complete (unless --no-wait is passed)

  • Retrieve and print the logs from CloudWatch when the task exits

This is ideal for debugging, re-running failed jobs, or triggering occasional maintenance tasks on demand.

SERVICES VS TASKS

A task of type daemon is launched as a long-running ECS service and benefits from restart policies and availability guarantees.

A task of type task is run using run-task and may run once, forever, or periodically - but it will not be automatically restarted if it fails.

S3 BUCKETS

The Fargate stack framework supports creating a new S3 bucket or using an existing one. The bucket can be used by your ECS tasks and services, and the framework will configure the necessary IAM permissions for access.

By default, full read/write access is granted unless you specify restrictions (e.g., read-only or path-level constraints). In this model, no bucket policy is required or modified.

Note: Full access includes s3:GetObject, s3:PutObject, s3:DeleteObject, and s3:ListBucket. Readonly access is limited to s3:GetObject and s3:ListBucket.

BASIC CONFIGURATION

You define a bucket in your configuration like this:

bucket:
  name: my-app-bucket

By default, this grants full read/write access to the entire bucket via the IAM role attached to your ECS task definition.

RESTRICTED ACCESS

You can limit access to a subset of the bucket using the readonly: and paths: keys:

bucket:
  name: my-app-bucket
  readonly: true
  paths:
    - public/*
    - logs/*

This will:

  • Grant only s3:GetObject and s3:ListBucket permissions

  • Limit access to the specified path prefixes

The paths: values are interpreted as S3 key prefixes and inserted directly into the role policy.

If you specify readonly: true but omit paths:, read-only access will apply to the entire bucket. If you omit both keys, full read/write access is granted.

IAM-BASED ENFORCEMENT

Bucket access is enforced exclusively through IAM role permissions. The framework does not modify or require an S3 bucket policy. This keeps your configuration simpler and avoids potential conflicts with externally managed bucket policies.

USING EXISTING BUCKETS

If you reference an existing bucket not created by the framework, be aware that the bucket's own policy may still restrict access.

In particular:

  • The IAM role created by the framework may permit access to a path

  • But a bucket policy with an explicit Deny will override that and block access

  • This restriction will only be discovered at runtime when your task attempts access

To avoid surprises, ensure that any bucket policy on an external bucket permits access from the IAM role you're configuring.

HTTP SERVICES

Overview

To create a Fargate HTTP service set the type: key in your task's configuration section to "http" or "https".

The task type ("http" or "https") determines:

  • the type of load balancer that will be used or created

  • whether or not a certificate will be used or created

  • what default port will be configured in your ALB's listener rule

Key Assumptions When Creating HTTP Services

  • Your domain is managed in Route 53 and your profile can create Route 53 record sets.

    Note: If your domain is managed in a different AWS account, set a separate profile: value in the route53: section of the configuration file. Your profile should have sufficient permissions to manage Route 53 recordsets.

  • Your Fargate task will be deployed in a private subnet and will listen on port 80.

  • No certificate will be provisioned for internal facing applications. Traffic by default to internal facing applications (those that use an internal ALB) will be insecure. This may become an option in the future.

Architecture

When you set your task type to "http" or "https" a default architecture depicted below will be provisioned.

        (optional)
    +------------------+
    |  Internet Client |
    +--------+---------+
             |
  [only if ALB is external]
             |
+------------v--------------+
|  Route 53 Hosted Zone     |
|  Alias: myapp.example.com |
|     --> ALB DNS Name      |
+----------+----------------+
             |
  +----------v----------+
  | Application Load    |
  | Balancer (ALB)      |
  | [internal or        |
  |  internet-facing]   |
  |                     |
  | Listeners:          |
  |   - Port 80         |
  |   - Port 443 w/ TLS |
  |     + ACM Cert      |
  |       (TLS/SSL)     |
  |     [if external]   |
  +----------+----------+
             |
      +------v-------+
      | Target Group |
      +------+-------+
             |
     +-------v---------+
     | ECS Service     |
     | (Fargate Task)  |
     +-------+---------+
             |
   +---------v----------+
   | VPC Private Subnet |
   +--------------------+

This default architecture provides a repeatable, production-ready deployment pattern for HTTP services with minimal configuration.

Behavior by Task Type

For HTTP services, you set the task type to either "http" or "https" (these are the only options that will trigger a task to be configured for HTTP services). The table below summarizes the configurations by task type.

+-------+----------+-------------+-----------+---------------+
| Type  | ALB type | Certificate |    Port   |  Hosted Zone  |
+-------+----------+-------------+-----------+---------------+
| http  | internal |    No       |    80     |   private     |
| https | external |   Yes       |   443     |   public      |
|       |          |             | 80 => 443 |               |
+-------+----------+-------------+-----------+---------------+

NOTE: You must provide a domain name for both an internal and external facing HTTP service. This also implies you must have a both a private and public hosted zone for your domain.

Your task type will also determine which type of subnet is required and where to search for an existing ALB to use. If you want to prevent re-use of an existing ALB and force the creation of a new one use the --create-alb option when you run your first plan.

In your initial configuration you do not need to specify the subnets or the hosted zone id. The framework will discover those and report if any required resources are unavailable. If the task type is "https", the script looks for a public zone, public subnets and an internet-facing ALB otherwise it looks for a private zone, private subnets and an internal ALB.

ACM Certificate Management

If the task type is "https" and no ACM certificate currently exists for your domain, the framework will automatically provision one. The certificate will be created in the same region as the ALB and issued via AWS Certificate Manager. If the certificate is validated via DNS and subsequently attached to the listener on port 443.

Port and Listener Rules

For external-facing apps, a separate listener on port 80 is created. It forwards traffic to port 443 using a default redirect rule (301). If you do not want a redirect rule, set the redirect_80: in the alb: section to "false".

If you want your internal application to listen on a port other than 80, set the port: key in the alb: section to a new port value.

Example Minimal Configuration

app:
  name: http-test
domain: http-test.example.com
task:
  apache:
    type: http
    image: http-test:latest

Based on this minimal configuration app-FargateStack will enrich the configuration with appropriate defaults and proceed to provision your HTTP service.

To do that, the framework attempts to discover the resources required for your service. If your environment is not compatible with creating the service, the framework will report the missing resources and abort the process.

Given this minimal configuration for an internal ("http") or external ("https") HTTP service, discovery entails:

...determining your VPC's ID
...identifying the private subnet IDs
...determining if there is and existing load balancer with the correct scheme
...finding your load balancer's security group (if an ALB exists)
...looking for a listener rule on port 80 (and 443 if type is "https"), including a default forwarding redirect rule
...validating that you have a private or public hosted zone in Route 53 that supports your domain
...setting other defaults for additional resources to be built (log groups, cluster, target group, etc)
...determining if an ACM certificate exists for your domain (if type is "https")

Note: Discovery of these resources is only done when they are missing from your configuration. If you have multiple VPCs for example you can should explicitly set vpc_id: in the configuration to identify the target VPC. Likewise you can explicitly set other resource configurations (subnets, ALBs, Route 53, etc).

Resources are provisioned and your configuration file is updated incrementally as app-FargateStack compares your environment to the environment required for your stack. When either plan or apply complete your configuration is updated giving you complete insight into what resources were found and what resources will be provisioned. See CONFIGURATION for complete details on resource configurations.>

Your environment will be validated against the criteria described below.

  • You have at least 2 private subnets available for deployment

    Technically you can launch a task with only 1 subnet but for services behind an ALB Fargate requires 2 subnets.

    When you create a service with a load balancer, you must specify two or more subnets in different Availability Zones. - AWS Docs

  • You have a hosted zone for your domain of the appropriate type (private for type "http", public for type "https")

As discovery progresses, existing and required resources are logged and your configuration file is updated. If you are NOT running in dryrun mode, resources will be created immediately as they are discovered to be missing from your environment.

Application Load Balancer

When you provision an HTTP service, whether or not it is secure, the service will placed behind an application load balancer. Your Fargate service is created in private subnets, so your VPC must contain at least two private subnets. Your load balancer can either be internally or externally facing.

By default, the framework looks for and will reuse a load balancer with the correct scheme (internal or internet-facing), in a subnet aligned with your task type. The ALB will be placed in public subnets if it is internet-facing. You can override that behavior by either explicitly setting the ALB arn in the alb: section of the configuration or pass --create-alb when you run our plan and apply.

If no ALB is found or you passed the --create-alb option, a new ALB is provisioned. When creating a new ALB, app-FargateStack will also create the necessary listeners and listener rules for the ports you have configured.

Why Does the Framework Force the Use of a Load Balancer?

While it is possible to avoid the use or the creation of a load balancer for your service, the framework forces you to use one for at least two reasons. Firstly, the IP address of your service may not be stable and is not friendly for development or production purposes. The framework is, after all trying its best to promote best practices while preventing you from having to know how all the sausage is made.

Secondly, it is almost guaranteed that you will eventually want a domain name for your production service - whether it is an internally facing microservice or an externally facing web application.

Creating an alias in Route 53 for your domain pointing to the ALB ensures you don't need to update application configurations with the service's dynamic IP address. Additionally, using a load balancer allows you to create custom routing rules to your service. If you want to run multiple tasks for your service to support handling more traffice a load balancer is required.

With those things in mind the framework automatically uses an ALB for HTTP services and creates an alias record (A) for your domain for both internal and external facing services.

AWS WAF Support

For external-facing HTTPS services, App::FargateStack can automate the creation and association of an AWS Web Application Firewall (WAF) to provide an essential layer of security. This protects your application from common web exploits and bots that could affect availability or compromise security.

The framework follows a "Hybrid Management Model" for WAF, designed to provide a secure, sensible baseline out-of-the-box while giving you full control over fine-grained rule customization.

Enabling WAF Protection

To enable WAF, simply add a waf block with enabled: true to your alb configuration:

alb:
  # ... existing alb configuration ...
  waf:
    enabled: true

Configuring Managed Rules

To simplify configuration, App::FargateStack uses a keyword-based system for enabling AWS Managed Rule Groups. You can specify a list of keywords under the managed_rules key in your waf configuration.

If the managed_rules key is omitted, the framework will apply the default bundle, which provides a strong and cost-effective security baseline.

waf:
  enabled: true
  managed_rules: [linux-app, admin, -php]

The framework supports both individual rule sets and pre-configured "bundles" for common application types. It also supports a subtractive syntax (prefixing a keyword with a -) to remove rule sets from a bundle.

Rule Set Keywords

  • base: A strong baseline including AWSManagedRulesCommonRuleSet, AWSManagedRulesAmazonIpReputationList, and AWSManagedRulesKnownBadInputsRuleSet.

  • admin: Protects exposed administrative pages (AWSManagedRulesAdminProtectionRuleSet).

  • sql: Protects against SQL injection attacks (AWSManagedRulesSQLiRuleSet).

  • linux: Includes rules for Linux and Unix-like environments.

  • php: Includes rules for applications running on PHP.

  • wordpress: Includes rules specific to WordPress sites.

  • windows: Includes rules for Windows Server environments.

  • anonymous: Use with caution. Blocks traffic from anonymous sources like VPNs and proxies, which may block legitimate users.

  • ddos: Mitigates application-layer (Layer 7) DDoS attacks like HTTP floods.

  • premium: Warning: Extra Cost. Enables advanced, paid protections for bot control and account takeover prevention.

Rule Bundles

  • default: Includes base and sql. This is the recommended starting point for most applications.

  • linux-app: Includes default and linux.

  • wordpress-app: Includes default, linux, and wordpress.

  • windows-app: Includes default and windows.

  • all: Includes all standard, non-premium rule sets. Warning: This will likely exceed the default WCU quota and may incur additional costs.

The Bootstrap Process (First Run)

On the first apply run with WAF enabled, the framework will perform a one-time bootstrap:

  1. It generates a default web-acl.json file in your project directory. This file contains the complete definition of your Web ACL, including the rules generated from your managed_rules keywords.

  2. It calls aws wafv2 create-web-acl to create a new Web ACL.

  3. It calls aws wafv2 associate-web-acl to link the new Web ACL to your Application Load Balancer.

  4. It updates your configuration file with the state of the new WAF resources, including its Name, ID, ARN, LockToken, and a checksum of the web-acl.json file.

  5. The waf block in your fargate-stack.yml is updated to reflect the bootstrapped state. If the managed_rules key was not present, it will be added with the default value of [default].

Ongoing Management (Subsequent Runs)

After the initial creation, you take full control of the rules. To add, remove, or modify rules, you simply edit the web-acl.json file directly.

On subsequent runs of apply, App::FargateStack will:

  • Calculate a checksum of your web-acl.json file.

  • If the checksum has changed, it will safely update the remote Web ACL with your new rule set.

  • If the checksum has not changed, it will skip the update to avoid unnecessary API calls.

This model gives you the best of both worlds: the "minimal configuration, maximum results" of a secure default, and the full "transparent box" control to customize your security posture as your application's needs evolve.

Conflict and Drift Management

The framework includes robust safety checks to prevent accidental data loss. If it detects that the Web ACL has been modified in the AWS Console and you have also modified your local web-acl.json file, it will detect the state conflict, refuse to make any changes, and provide a clear error message with instructions on how to resolve it.

Estimated Cost

The default WAF configuration is designed to provide a strong security baseline while remaining cost-effective. When you enable WAF without specifying any managed_rules, the framework applies the default bundle, which includes the base and sql rule sets.

The approximate monthly cost for this default configuration is ~$9.00 per month, plus per-request charges.

The cost is broken down as follows:

  • $5.00 / month for the Web ACL itself.

  • $4.00 / month for the four AWS Managed Rule Groups included in the default bundle (3 in 'base', 1 in 'sql').

  • $0.60 / per 1 million requests processed by the Web ACL.

Warning: Enabling the premium rule set will incur significant additional monthly and per-request fees for services like Bot Control and Account Takeover Prevention. Always review the AWS WAF pricing page before enabling premium features.

Roadmap for HTTP Services

  • path based routing on ALB listeners

AUTOSCALING

Overview

For services that experience variable load, such as HTTP applications or background job processors, App::FargateStack can automate the process of scaling the number of running tasks up or down to meet demand. This ensures high availability during traffic spikes and saves costs during quiet periods.

The framework integrates with AWS Application Auto Scaling to provide target tracking scaling policies. This allows you to define a target metric - such as average CPU utilization or the number of requests per minute - and the framework will automatically manage the number of Fargate tasks to keep that metric at your desired level.

Enabling Autoscaling

To enable autoscaling for a service, add an autoscaling block to its task configuration in your .yml configuration file.

tasks: my-service: # ... other task settings ... autoscaling: min_capacity: 1 max_capacity: 10 cpu: 60

Configuration Parameters

The autoscaling block accepts the following keys:

  • min_capacity (Required)

    The minimum number of tasks to keep running at all times. The service will never scale in below this number.

  • max_capacity (Required)

    The maximum number of tasks that the service can scale out to. This acts as a safeguard to control costs.

  • cpu OR requests (Required, mutually exclusive)

    You must specify exactly one scaling metric.

    • cpu: The target average CPU utilization percentage across all tasks in the service. Valid values are between 1 and 100.

    • requests: The target number of requests per minute for each task. This is only valid for tasks of type http or https that are behind an Application Load Balancer.

  • scale_in_cooldown (Optional)

    The amount of time, in seconds, to wait after a scale-in activity before another scale-in activity can start. This prevents the service from scaling in too aggressively.

    Default: 300

  • scale_out_cooldown (Optional)

    The amount of time, in seconds, to wait after a scale-out activity before another scale-out activity can start. This allows new tasks time to warm up and start accepting traffic before the service decides to scale out again.

    Default: 60

  • policy_name (Managed by CApp::FargateStack)

    This is a unique name for the scaling policy generated by the framework. It is written to your configuration file and used to detect drift between your configuration and the live environment in AWS. You should not modify this value.

Example: Scaling on CPU Utilization

This configuration will maintain at least 1 task, scale up to a maximum of 5 tasks, and will add or remove tasks to keep the average CPU utilization at or near 60%.

tasks:
  my-cpu-intensive-worker:
    type: daemon
    image: my-worker:latest
    autoscaling:
      min_capacity: 1
      max_capacity: 5
      cpu: 60

Example: Scaling on ALB Requests

This configuration will maintain at least 2 tasks, scale up to a maximum of 20 tasks, and will add or remove tasks to keep the number of requests per minute for each task at or near 1000. It also specifies custom cooldown periods.

tasks:
  my-website:
    type: https
    image: my-website:latest
    autoscaling:
      min_capacity: 2
      max_capacity: 20
      requests: 1000
      scale_in_cooldown: 600
      scale_out_cooldown: 120

Scheduled Scaling Configuration

To configure predictive, time-based scaling, add a scheduled block inside the main autoscaling configuration. This allows you to define named time windows for scaling.

Example:

autoscaling:
  ...
  scheduled:
    business_hours:
      start_time: 00:18
      end_time: 00:02
      min_capacity: 2/1
      max_capacity: 3/2

Note: start_time and end_time are UTC

  • scheduled (Optional)

    A hash where each key is a unique, descriptive name for the schedule group (e.g., business_hours). Each group defines a time window and the capacity changes for that window.

    • start_time (Required): The time to scale up, in HH:MM format (24-hour clock, UTC).

    • end_time (Required): The time to scale down, in HH:MM format (24-hour clock, UTC).

    • days (Required): The days of the week for the schedule. Can be a range (e.g., MON-FRI) or comma-separated values.

    • min_capacity (Optional): The minimum capacity during and outside the window. The two values should be separated by a slash, comma, colon, hyphen, or space (e.g., 2/1 or 2,1).

    • max_capacity (Optional): The maximum capacity during and outside the window, using the same in/out format as min_capacity.

The parser will generate two scheduled actions from this block: one to apply the "in" capacity at the start_time and one to apply the "out" capacity at the end_time.

Example: Combined Metric and Scheduled Scaling

This configuration creates a robust scaling strategy. The service will reactively scale based on CPU load at all times, but the capacity "guardrails" will be adjusted automatically for business hours.

tasks:
  my-website:
    type: https
    image: my-website:latest
    autoscaling:
      # Default metric-based scaling policy
      min_capacity: 1
      max_capacity: 10
      cpu: 75

      # Scheduled scaling actions to adjust the guardrails
      schedule:
        business_hours:
          start_time: "09:00"
          end_time: "18:00"
          days: MON-FRI
          min_capacity: 2/1
          max_capacity: 10/2

Drift Detection and Management

CApp::FargateStack treats your YAML configuration as the single source of truth. On every plan or apply run, it will compare the autoscaling configuration in your file with the live scaling policy in AWS.

If it detects any differences (e.g., someone manually changed the max capacity in the AWS Console), it will report the drift and will not apply any changes. To overwrite the live settings and enforce the configuration from your file, you must re-run the apply command with the --force flag. This provides a critical safety check against accidental configuration changes.

The autoscaling keyword

For any service type (https, http, daemon, or task), you can enable and configure autoscaling directly from the command line. This provides a quick-start method to make your service elastic from the moment it's created.

The Cautoscaling: keyword accepts a metric and an optional target value:

  • Enable with a specific target value:

    autoscaling:requests=500 autoscaling:cpu=60

    This will enable autoscaling and set the target for either ALB requests per task or average CPU utilization.

  • Enable with default target value:

    autoscaling:requests autoscaling:cpu

    If you omit the target value, a sensible default will be used (e.g., 500 for requests, 60 for CPU).

When the create-stack command sees the Cautoscaling: keyword, it will generate a complete autoscaling block in your fargate-stack.yml file. This block will be populated with safe defaults (min_capacity: 1, max_capacity: 2), the specified metric, and all other necessary fields, making it easy to review and customize later. See "AUTOSCALING" for a full list of configuration options.

CURRENT LIMITATIONS

  • Stacks may contain multiple daemon services, but only one task may be exposed as an HTTP/HTTPS service via an ALB.

  • Limited configuration options for some resources such as advanced load balancer listener rules, custom CloudWatch metrics, or task health check tuning.

  • Some out of band infrastructure changes may break the ability to re-run app-FargateStack without manually updating the configuration

  • Support for only 1 EFS filesystem per task

  • This framework assumes that the operatingSystemFamily is "LINUX" and the cpuArchitecture is "X86_64" LINUX. This is unlikely to change.

TROUBLESHOOTING

Warning: task placed in a public subnet

When running a task you may see:

[2025/08/05 03:40:58] run-task: subnet-id: [subnet-7c160c37] is in a public subnet...consider running your jobs in a private subnet

This means the task is being scheduled in a subnet that has a 0.0.0.0/0 route to an Internet Gateway (a public subnet).

While not fatal, placing tasks in public subnets is discouraged unless you have a specific need.

Why this matters

Running tasks in public subnets can introduce risk and operational surprises:

  • Accidental exposure

    If the task is assigned a public IP and the security group allows inbound access, it may be reachable from the internet.

  • Unintended dependency

    Public-subnet egress typically relies on a public IP and the Internet Gateway. That can bypass intended egress controls, logging, or central inspection.

  • Narrow security margin

    Safety depends entirely on security groups and NACLs. A small misconfiguration can expose services or data.

Use private subnets for most Fargate workloads. Private subnets do not route directly to the internet.

If the task needs outbound access (for example, to pull images from ECR or call external APIs), use one of:

  • A NAT Gateway (private subnet egress to the internet)

  • VPC interface endpoints for ECR (ecr.api and ecr.dkr) and a gateway endpoint for S3, so image pulls stay inside the VPC with no public IPs

For public-facing applications, the common pattern is: tasks in private subnets, fronted by a public Application Load Balancer in public subnets.

When is a public subnet acceptable?

Use a public subnet only when the task itself must have a public IP and terminate client connections directly (uncommon). If you do:

  • Set assignPublicIp=ENABLED so the task can reach the internet via the Internet Gateway

  • Keep security groups locked down and monitor egress on TCP 443

Note on image pulls

To pull from ECR, the task needs a path to ECR API, ECR DKR, and S3:

  • Public subnet: requires a public IP (assignPublicIp=ENABLED), unless you provision VPC endpoints

  • Private subnet: works via a NAT Gateway, or entirely private via VPC endpoints (no public IPs)

My task fails with this message:

ResourceInitializationError: unable to pull secrets or registry auth:
The task cannot pull registry auth from Amazon ECR: There is a
connection issue between the task and Amazon ECR. Check your task
network configuration. operation error ECR: GetAuthorizationToken,
exceeded maximum number of attempts, 3, https response error
StatusCode: 0, RequestID: , request send failed, Post
"https://api.ecr.us-east-1.amazonaws.com/": dial tcp 44.213.79.10:443:
i/o timeout

This error usually occurs when your task is launched in a subnet that does not have outbound access to the internet. Internet access - or a properly configured VPC endpoint - is required for Fargate to authenticate with ECR and pull your container image.

Common causes

  • The task was placed in a public subnet but was not assigned a public IP.

  • The task was placed in a private subnet without access to a NAT gateway or VPC endpoints.

Even though the subnet may have a route to an Internet Gateway (i.e., it is technically a "public" subnet), if the task does not receive a public IP, it cannot use that route to reach external services like ECR or Secrets Manager.

How to fix it

  • If using public subnets, ensure the task is assigned a public IP.

  • If using private subnets, ensure a NAT gateway is available and the subnet has a route to it.

  • Alternatively, configure VPC endpoints for ECR, Secrets Manager, and related services to avoid needing internet access altogether.

Note on Subnet Selection

App::FargateStack attempts to prevent this situation by analyzing your VPC configuration during planning. It categorizes subnets as private or public and evaluates whether they provide the necessary network access to launch a Fargate task successfully. The framework warns if you attempt to use a subnet that lacks internet or endpoint access.

My task failed to start and the reason is unclear

This is one of the most common and frustrating scenarios when working with Fargate. You run start-service or run-task, the command seems to succeed, but then the task quickly stops. The status command shows the desired count is 1 but the running count is 0, and the logs are empty.

This often happens due to a resource initialization error. The problem isn't with your container image itself, but with the infrastructure Fargate is trying to set up for it.

Common causes include:

  • Networking Issues: The task is in a subnet that can't pull the image from ECR (e.g., no NAT Gateway or VPC endpoints).

  • Permissions Errors: The task's IAM role is missing a required permission.

  • EFS Mount Failures: The task cannot mount an EFS volume, often due to a misconfigured security group or incorrectly specified path.

These errors are opaque because they happen deep inside the AWS-managed environment. The high-level ECS API only reports a generic failure, and since it's not an API call error, it won't appear in CloudTrail.

The Solution: Finding the stoppedReason

To solve this, App-FargateStack provides an optional argument to the list-tasks command. By default, this command only shows RUNNING tasks. However, if you add the stopped argument, it will show recently stopped tasks and, most importantly, the reason they stopped.

The Command:

app-FargateStack list-tasks stopped

This will display a table of stopped tasks, including a Stopped Reason column. This column often contains the detailed, multi-line error message from the underlying AWS service that caused the failure, giving you the exact information you need to debug the problem.

For example, if an EFS mount failed, the stoppedReason might contain:

ResourceInitializationError: failed to invoke EFS utils
commands... mount.nfs4: mounting failed, reason given by server: No
such file or directory

This tells you immediately that the problem is with the EFS path, not a generic "task failed" message.

Why is my task or service still using an old image?

This is one of the most common points of confusion when working with ECS and Fargate.

You may have just built and pushed a new image to ECR using the same tag (e.g. latest), but when you launch a task or deploy a service, ECS appears to continue using the old image. Here's why.

One-off tasks: run-task uses a fixed image digest

When you run a task using:

app-FargateStack run-task my-task

ECS uses the exact task definition revision as registered. If the image was specified using a tag like :latest, ECS resolves that tag once -- at the time the task starts -- and stores the resolved digest (e.g. sha256:...).

This means:

  • Tasks launched this way will continue to run the old image, even if the latest tag in ECR now points to a newer image.

  • The only way to run a task with the new image is to register a new task definition that references the updated image. You can force a new task definition by registering the definition.

    app-FargateStack register my-task

Services: create-service and update-service use frozen images too

When you create or update a service, ECS also resolves any image tags to their current digest and stores that in the registered task definition.

This means that ECS services are also tied to the image that existed at the time of task definition registration.

If you push a new image to ECR using the same tag (e.g. :latest), the service will not automatically use it. ECS does not re-resolve the tag unless you explicitly tell it to.

--force-new-deployment re-pulls image tags (if not pinned by digest)

If your task definition references the image by tag (e.g. http-service:latest), and not by digest, then running:

app-FargateStack redeploy my-service

will cause ECS to:

  • Stop the currently running tasks

  • Start new tasks using the same task definition revision

  • Re-resolve and pull the image tag from ECR

This allows your service to pick up a newly pushed image without registering a new task definition, as long as the task definition used a tag (not a digest).

Confirm what your task definition is using

To see whether your task definition uses a tag or a digest, run:

aws ecs describe-task-definition --task-definition my-task:42

Look at the image field under containerDefinitions. It will either be:

image: http-service:latest     # tag -- will be re-resolved by --force-new-deployment
image: http-service@sha256:... # digest -- frozen, cannot be re-resolved

Best practices

  • Avoid using :latest in production. Use immutable tags (e.g. :v1.2.3) or digests.

  • If you want to deploy a new image, the safest and most deterministic approach is to:

    - Build and push the image using a new tag or digest
    - Register a new task definition revision referencing that tag or digest
    - Update your service to use the new task definition
  • Use --force-new-deployment only if your task definition uses a tag and you want to re-resolve it without changing the task definition itself.

ROADMAP

  • Scaling configuration

  • Service Connect, including certificates for internal HTTP services

  • Multiple HTTP services

  • Path based routing

SEE ALSO

IPC::Run, App::Command, App::AWS, CLI::Simple

AUTHOR

Rob Lauer - rclauer@gmail.com

LICENSE

This script is released under the same terms as Perl itself.

4 POD Errors

The following errors were encountered while parsing the POD:

Around line 367:

Expected '=item *'

Around line 370:

Expected '=item *'

Around line 384:

Expected '=item *'

Around line 386:

Expected '=item *'