This guide is valid for users of self-hosted Prefect server or Prefect Cloud users with a tier that allows hybrid work pools.
This guide walks you through manually setting up ECS infrastructure to run Prefect workers. For architecture concepts and overview, see the ECS Worker overview.

Prerequisites

You will need the following to successfully complete this guide:
For production deployments, it is recommended that you create your own VPC with appropriate security policies based on your organization’s recommendations.If you want to create a new VPC for this guide, follow the VPC creation guide.

Create the Prefect ECS work pool

First, create an ECS work pool for your deployments to use. You can do this either from the CLI or the Prefect UI. If doing so from the CLI, be sure to authenticate with Prefect Cloud or run a local Prefect server instance.
Run the following command to create a new ECS work pool named my-ecs-pool:
prefect work-pool create --type ecs my-ecs-pool
Because this guide uses Fargate as the capacity provider, this step requires no further action.

Create a Secret for the Prefect API key

If you are using a Prefect self-hosted server and have authentication disabled, you can skip this step.
The Prefect worker needs to authenticate with your Prefect server to poll the work pool for flow runs. For authentication, you must provide a Bearer token (PREFECT_API_KEY) or Basic Auth string (PREFECT_API_AUTH_STRING) to the Prefect API. As a security best practice, we recommend you store your Prefect API key in AWS Secrets Manager or Systems Manager Parameter Store.
1

Find your secret

You can find your Prefect API key several ways:
2

Create a secret

Choose between AWS Secrets Manager or Systems Manager Parameter Store to store your Prefect API key. Both services allow you to securely store and manage sensitive information such as API keys, passwords, and other secrets.
To create a Secret in AWS Secrets Manager, use the aws secretsmanager create-secret command:
aws secretsmanager create-secret --name PrefectECSWorkerAPIKey --secret-string '<your-prefect-api-key>'
Make a note of the Amazon Resource Name (ARN) of the secret that is returned in the command output. You will need it later when configuring the ECS worker task definition.

Create the AWS IAM resources

We will create two IAM roles:
  1. ecsTaskExecutionRole: This role will be used by ECS to start ECS tasks.
  2. ecsTaskRole: This role will contain the permissions required by Prefect ECS worker in order to run your flows as ECS tasks.
The role permissions are based on the principle of least-privilege, meaning that each role will only have the permissions it needs to perform its job.

Create a trust policy

The trust policy will allow the ECS service containing the Prefect worker to assume the role required for calling other AWS services. This is called a service-linked role. The trust policy is a JSON document that specifies which AWS service can assume the role. Save this policy to a file, such as trust-policy.json:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ecs-tasks.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
Alternately, you can download this file using the following command:
curl -O https://raw.githubusercontent.com/PrefectHQ/prefect/refs/heads/main/docs/integrations/prefect-aws/ecs/iam/trust-policy.json

Create the IAM roles

Now, we will create the IAM roles that will be used by the ECS worker.

Create the ECS task execution role

The ECS task execution role will be used to start the ECS worker task. We will assign it a minimal set of permissions to allow the worker to pull images from ECR and publish logs to CloudWatch.
1

Create the role

Create the role using the aws iam create-role command:
aws iam create-role --role-name ecsTaskExecutionRole --assume-role-policy-document file://trust-policy.json
Make a note of the ARN (Amazon Resource Name) of the role that is returned in the command output. You will need it later when creating the ECS task definition.
2

Create the Secret Policy

The following is a minimal policy that grants the necessary permissions for ECS to obtain the current value of the secret and inject it into the ECS task. Save this policy to a file, such as secret-policy.json:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "secretsmanager:GetSecretValue",
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:secretsmanager:<region>:<account-id>:secret:PrefectECSWorkerAPIKey"
        }
    ]
}
Alternately, you can download this file using the following command:
curl -O https://raw.githubusercontent.com/PrefectHQ/prefect/refs/heads/main/docs/integrations/prefect-aws/ecs/iam/secrets/secrets-manager/secret-policy.json
3

Register the policy

Create a new IAM policy named ecsTaskExecutionPolicy using the policy document you just created.
aws iam create-policy --policy-name ecsTaskExecutionPolicy --policy-document file://secret-policy.json
4

Attach the Policies

The AmazonECSTaskExecutionRolePolicy managed policy grants the minimum permissions necessary for starting ECS tasks. See here for other common execution role permissions.Attach this policy to your task execution role using the aws iam attach-role-policy:
aws iam attach-role-policy --role-name ecsTaskExecutionRole --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Attach the custom policy you created in the previous step so that the ECS task can access the Prefect API key stored in AWS Secrets Manager or Systems Manager Parameter Store:
aws iam put-role-policy --role-name ecsTaskExecutionRole --policy-name PrefectECSWorkerSecretPolicy --policy-document file://secret-policy.json

Create the worker ECS task role

The worker ECS task role will be used by the Prefect worker to interact with the AWS API to run flows as ECS containers. This role will require the ability to describe, register, and deregister ECS task definitions, as well as the ability to start and stop ECS tasks.
1

Create the role

Use the following command to create the role. The same trust policy is also used for this role.
aws iam create-role --role-name ecsTaskRole --assume-role-policy-document file://trust-policy.json
2

Create the task policy

The following is a minimal policy that grants the necessary permissions for the Prefect ECS worker to run your flows as ECS tasks. Save this policy to a file, such as worker-policy.json:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "ec2:DescribeSubnets",
        "ec2:DescribeVpcs",
        "ecs:DeregisterTaskDefinition",
        "ecs:DescribeTaskDefinition",
        "ecs:DescribeTasks",
        "ecs:RegisterTaskDefinition",
        "ecs:RunTask",
        "ecs:StopTask",
        "ecs:TagResource",
        "iam:PassRole",
        "logs:GetLogEvents",
        "logs:PutLogEvents"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
Alternately, you can download this file using the following command:
curl -O https://raw.githubusercontent.com/PrefectHQ/prefect/refs/heads/main/docs/integrations/prefect-aws/ecs/iam/worker-policy.json
3

Register the policy

Create a new IAM policy named ecsTaskPolicy using the policy document you just created.
aws iam create-policy --policy-name ecsTaskPolicy --policy-document file://worker-policy.json
4

Attach policy to the role

Attach the custom ecsTaskPolicy to the ecsTaskRole so that the Prefect worker can dispatch flows to ECS:
aws iam attach-role-policy --role-name ecsTaskRole --policy-arn arn:aws:iam::<your-account-id>:policy/ecsTaskPolicy
Replace <your-account-id> with your AWS account ID.

Create an ECS task role for Prefect flows

This step is optional, but recommended if your flows require access to other AWS services.
Depending on the requirements of your flows, it is advised to create a separate role for your ECS tasks. This role will contain the permissions required by the ECS tasks in which your flows will run. For example, if your workflow loads data into an S3 bucket, you would need a role with additional permissions to access S3.

Configure event monitoring infrastructure

To enable the ECS worker to monitor and update the status of flow runs, we need to set up SQS queues and EventBridge rules that capture ECS task state changes. This infrastructure allows the worker to:
  • Track when ECS tasks (flow runs) start, stop, or fail
  • Update flow run states in real-time based on ECS task events
  • Provide better observability and status reporting for your workflows
This step sets up the same event monitoring infrastructure that the prefect-aws ecs-worker deploy-events command creates automatically. The worker will use the environment variable PREFECT_INTEGRATIONS_AWS_ECS_OBSERVER_SQS_QUEUE_NAME to discover and read from the events queue.
1

Create SQS queues for event monitoring

Create an SQS queue to receive ECS task state change events and a dead-letter queue for handling failed messages.First, create the dead-letter queue:
aws sqs create-queue --queue-name my-ecs-pool-events-dlq --attributes MessageRetentionPeriod=1209600,VisibilityTimeout=60
Get the ARN of the dead-letter queue:
aws sqs get-queue-attributes --queue-url $(aws sqs get-queue-url --queue-name my-ecs-pool-events-dlq --query 'QueueUrl' --output text) --attribute-names QueueArn --query 'Attributes.QueueArn' --output text
Now create the main queue with the dead-letter queue configured:
aws sqs create-queue \
  --queue-name my-ecs-pool-events \
  --attributes '{
    "MessageRetentionPeriod": "604800",
    "VisibilityTimeout": "300",
    "RedrivePolicy": "{\"deadLetterTargetArn\":\"<dlq-arn>\",\"maxReceiveCount\":3}"
  }'
Replace <dlq-arn> with the ARN of the dead-letter queue from the previous step, and my-ecs-pool with your work pool name.
The queue name should follow the pattern {work-pool-name}-events for consistency with the automated deployment.
2

Configure SQS queue policy

Allow EventBridge to send messages to your SQS queue by updating the queue policy:
aws sqs set-queue-attributes \
  --queue-url $(aws sqs get-queue-url --queue-name my-ecs-pool-events --query 'QueueUrl' --output text) \
  --attributes '{"Policy":"{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"events.amazonaws.com\"},\"Action\":[\"sqs:SendMessage\",\"sqs:GetQueueAttributes\",\"sqs:GetQueueUrl\"],\"Resource\":\"<queue-arn>\"}]}"}'
Replace <queue-arn> with the ARN of the queue created in the previous step.
3

Create EventBridge rule for ECS task state changes

Create an EventBridge rule to capture ECS task state changes and send them to the SQS queue:
aws events put-rule \
  --name my-ecs-pool-task-state-changes \
  --event-pattern '{
    "source": ["aws.ecs"],
    "detail-type": ["ECS Task State Change"],
    "detail": {
      "clusterArn": ["arn:aws:ecs:<region>:<account-id>:cluster/<cluster-name>"]
    }
  }' \
  --description "Capture ECS task state changes for Prefect worker" \
  --state ENABLED
Replace:
  • <region> with your AWS region
  • <account-id> with your AWS account ID
  • <cluster-name> with your ECS cluster name
  • my-ecs-pool with your work pool name
4

Add SQS queue as EventBridge rule target

Get the queue ARN and add it as a target for the EventBridge rule:
aws events put-targets \
  --rule my-ecs-pool-task-state-changes \
  --targets "Id=1,Arn=<queue-arn>"
Replace <queue-arn> with the ARN of the queue created in step 1.
5

Update worker task role with SQS permissions

Add SQS permissions to the worker task role created earlier:Create a file named sqs-policy.json:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sqs:ReceiveMessage",
                "sqs:DeleteMessage",
                "sqs:GetQueueAttributes",
                "sqs:GetQueueUrl",
                "sqs:ChangeMessageVisibility"
            ],
            "Resource": "arn:aws:sqs:<region>:<account-id>:my-ecs-pool-events"
        }
    ]
}
Replace <region>, <account-id>, and my-ecs-pool-events with your values.Apply the policy to the worker task role:
aws iam put-role-policy \
  --role-name ecsTaskRole \
  --policy-name EcsWorkerSqsPolicy \
  --policy-document file://sqs-policy.json

Creating the ECS worker service

Now that all the AWS IAM roles and event monitoring infrastructure have been created, we can deploy the Prefect worker to the ECS cluster.
1

Create the task definition

This task definition will be used to run the Prefect worker in an ECS task.Ensure you replace the placeholders for:
  • <ecs-task-execution-role-arn> with the ARN of the ecsTaskExecutionRole you created in Step 2.
  • <ecs-task-role-arn> with the ARN of the ecsTaskRole you created in Step 2.
  • <prefect-api-url> with the URL of your Prefect Server.
  • <aws-arn-of-secret> with the ARN of the resource from Secrets Manager or Systems Manager Parameter Store.
  • my-ecs-pool-events in the PREFECT_INTEGRATIONS_AWS_ECS_OBSERVER_SQS_QUEUE_NAME environment variable with your actual queue name from the event monitoring setup.
Save the following JSON to a file named task-definition.json:
{
    "family": "prefect-worker-task",
    "networkMode": "awsvpc",
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "512",
    "memory": "1024",
    "executionRoleArn": "<ecs-task-execution-role-arn>",
    "taskRoleArn": "<ecs-task-role-arn>",
    "containerDefinitions": [
        {
            "name": "prefect-worker",
            "image": "prefecthq/prefect:3-latest",
            "cpu": 512,
            "memory": 1024,
            "essential": true,
            "command": [
                "/bin/sh",
                "-c",
                "pip install prefect-aws && prefect worker start --pool my-ecs-pool --type ecs"
            ],
            "environment": [
                {
                    "name": "PREFECT_API_URL",
                    "value": "<prefect-api-url>"
                },
                {
                    "name": "PREFECT_INTEGRATIONS_AWS_ECS_OBSERVER_SQS_QUEUE_NAME",
                    "value": "my-ecs-pool-events"
                }
            ],
            "secrets": [
                {
                    "name": "PREFECT_API_KEY",
                    "valueFrom": "<aws-arn-of-secret>"
                }
            ]
        }
    ]
}
Alternately, you can download this file using the following command:
curl -O https://raw.githubusercontent.com/PrefectHQ/prefect/refs/heads/main/docs/integrations/prefect-aws/ecs/iam/task-definition.json
Notice that the CPU and Memory allocations are relatively small. The worker’s main responsibility is to submit work through API calls to AWS, not to execute your Prefect flow code.
To avoid hardcoding your API key into the task definition JSON see how to add sensitive data using AWS secrets manager to the container definition.
2

Register task definition

Before creating a service, you first need to register a task definition. You can do that using the register-task-definition command:
aws ecs register-task-definition --cli-input-json file://task-definition.json
Replace task-definition.json with the name of your task definition file.
3

Create the ECS service

Finally, create a service that will manage your Prefect worker.Ensure you replace the placeholders for:
  • <ecs-cluster> with the name of your ECS cluster.
  • <task-definition-arn> with the ARN of the task definition you just registered.
  • <subnet-ids> with a comma-separated list of your VPC subnet IDs.
  • Replace <security-group-ids> with a comma-separated list of your VPC security group IDs.
Use the aws ecs create-service command to create an ECS service running on Fargate for the Prefect worker:
aws ecs create-service --service-name prefect-worker-service --cluster <ecs-cluster> --task-definition <task-definition-arn> --launch-type FARGATE --desired-count 1 --network-configuration "awsvpcConfiguration={subnets=[<subnet-ids>],securityGroups=[<security-group-ids>],assignPublicIp='ENABLED'}"
4

Verify the Prefect worker is running

The work pool page in the Prefect UI allows you to check the health of your workers - make sure your new worker is live!
It may take a few minutes for the worker to come online after creating the service.
Refer to the troubleshooting section for further assistance if the worker isn’t online.

Configure work pool defaults

Now that your infrastructure is deployed, you should update your ECS work pool configuration with the resource identifiers so they don’t need to be specified on every deployment.
1

Update work pool via the UI

Navigate to your work pool in the Prefect UI and update the following fields in the Infrastructure tab:
  • Cluster ARN: Set to your ECS cluster ARN (e.g., arn:aws:ecs:us-east-1:123456789012:cluster/my-cluster)
  • VPC ID: Set to your VPC ID (e.g., vpc-12345678)
  • Subnets: Add your subnet IDs (e.g., subnet-12345678,subnet-87654321)
  • Execution Role ARN: Set to the task execution role ARN (e.g., arn:aws:iam::123456789012:role/ecsTaskExecutionRole)
These settings will be used as defaults for all deployments using this work pool, but can be overridden per deployment if needed.
2

Alternative: Update work pool via API

You can also update the work pool configuration programmatically using the Prefect API:
from prefect.client.schemas.objects import WorkPoolUpdate
from prefect import get_client

async def update_work_pool():
    async with get_client() as client:
        work_pool = await client.read_work_pool("my-ecs-pool")

        # Update base job template variables
        base_template = work_pool.base_job_template
        variables = base_template.get("variables", {})
        properties = variables.get("properties", {})

        # Update infrastructure defaults
        properties["cluster"] = {
            "default": "arn:aws:ecs:us-east-1:123456789012:cluster/my-cluster"
        }
        properties["vpc_id"] = {
            "default": "vpc-12345678"
        }
        properties["execution_role_arn"] = {
            "default": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole"
        }

        # Update network configuration
        network_config = properties.setdefault("network_configuration", {})
        network_props = network_config.setdefault("properties", {})
        awsvpc_config = network_props.setdefault("awsvpcConfiguration", {})
        awsvpc_props = awsvpc_config.setdefault("properties", {})
        awsvpc_props["subnets"] = {
            "default": ["subnet-12345678", "subnet-87654321"]
        }

        # Update work pool
        variables["properties"] = properties
        base_template["variables"] = variables

        await client.update_work_pool(
            "my-ecs-pool",
            WorkPoolUpdate(base_job_template=base_template)
        )

# Run the update
import asyncio
asyncio.run(update_work_pool())
Replace the ARNs and IDs with your actual resource identifiers.

Deploy a flow run to your ECS work pool

This guide uses the AWS Elastic Container Registry (ECR) to store a Docker image containing your flow code. To do this, we will write a flow, then deploy it using build and push steps that copy flow code into a Docker image and push that image to an ECR repository.
1

Write a simple test flow

my_flow.py
from prefect import flow
from prefect.logging import get_run_logger

@flow
def my_flow():
    logger = get_run_logger()
    logger.info("Hello from ECS!!")

if __name__ == "__main__":
    my_flow()
2

Create an ECR repository

Use the aws ecr create-repository command to create an ECR repository. The name you choose for your repository will be reused in the next step when defining your Prefect deployment.
aws ecr create-repository --repository-name <my-ecr-repo>
3

Create a `prefect.yaml` file

To have Prefect build your image when deploying your flow create a prefect.yaml file with the following specification:
prefect.yaml
name: ecs-worker-guide

pull:
- prefect.deployments.steps.set_working_directory:
    directory: /opt/prefect/ecs-worker-guide

# build section allows you to manage and build docker images
build:
- prefect_docker.deployments.steps.build_docker_image:
    id: build_image
    requires: prefect-docker>=0.3.1
    image_name: <my-ecr-repo>
    tag: latest
    dockerfile: auto

# push section allows you to manage if and how this project is uploaded to remote locations
push:
- prefect_docker.deployments.steps.push_docker_image:
    requires: prefect-docker>=0.3.1
    image_name: '{{ build_image.image_name }}'
    tag: '{{ build_image.tag }}'

 # the deployments section allows you to provide configuration for deploying flows
deployments:
- name: my_ecs_deployment
    version:
    tags: []
    description:
    entrypoint: flow.py:my_flow
    parameters: {}
    work_pool:
      name: my-ecs-pool
      work_queue_name:
      job_variables:
        image: '{{ build_image.image }}'
    schedules: []
4

Deploy the flow

Deploy the flow to the Prefect Cloud or your self-managed server instance.
prefect deploy my_flow.py:my_ecs_deployment
5

Run!

Find the deployment in the UI and click the Quick Run button!

Troubleshooting

If your worker does not appear in the Prefect UI, check the following:
  • Ensure that the ECS service is running and that the task definition is registered correctly.
  • Check the ECS service logs in CloudWatch to see if there are any errors.
  • Verify that the IAM roles have the correct permissions.
  • Ensure that the PREFECT_API_URL and PREFECT_API_KEY environment variables are set correctly in the task definition.
  • For self-hosted Prefect servers, ensure that you replaced PREFECT_API_KEY from the example with PREFECT_API_AUTH_STRING in the task definition.
  • Ensure your Prefect ECS worker has network connectivity to the Prefect API. If you are using a private VPC, ensure that there is a NAT gateway or internet gateway configured to allow outbound traffic to the Prefect API.

Event monitoring issues

If flow runs are not updating their status properly, check the event monitoring setup:
  • Verify the SQS queue was created and is receiving messages from EventBridge
  • Check that the EventBridge rule is active and properly configured for your ECS cluster
  • Ensure the worker task role has the necessary SQS permissions (sqs:ReceiveMessage, sqs:DeleteMessage, etc.)
  • Verify the PREFECT_INTEGRATIONS_AWS_ECS_OBSERVER_SQS_QUEUE_NAME environment variable is set correctly in the worker task definition
  • Check CloudWatch logs for any SQS-related errors in the worker logs

Next steps

Now that you are confident your ECS worker is healthy, you can experiment with different work pool configurations.
  • Do your flow runs require higher CPU?
  • Would an EC2 Launch Type speed up your flow run execution?
These infrastructure configuration values can be set on your ECS work pool or they can be overridden on the deployment level through job_variables if desired.