When a container misbehaves in production, you want to look inside it - check a file, read an environment variable, run a diagnostic. On Fargate there is no host to SSH into and no docker exec. ECS Exec solves this: it opens a command channel straight into a running container, tunneled securely through AWS Systems Manager, no inbound ports or SSH keys required.
This page shows how to enable it, grant the right permissions, and run commands with the ExecuteCommand API.
The flag alone is not enough: the task role must carry SSM messaging permissions (covered in the Deep Dive), or the exec agent inside the task cannot open its channel to Systems Manager.
Once a task is running with exec enabled, ExecuteCommand runs a command inside a named container. The API returns a session (a WebSocket URL and token). For a truly interactive shell you drive that session through the Session Manager plugin, which is why the AWS CLI is the usual front end - but the SDK call is what enables tooling and non-interactive commands.
# --- Python (boto3) ---import boto3ecs = boto3.client("ecs", region_name="us-east-1")resp = ecs.execute_command( cluster="prod", task="arn:aws:ecs:us-east-1:111122223333:task/prod/abc123", container="api", command="/bin/sh", interactive=True,)session = resp["session"]print(session["sessionId"])# session["streamUrl"] + session["tokenValue"] are handed to the# Session Manager plugin to drive an interactive terminal.
// --- TypeScript (AWS SDK v3) ---import { ECSClient, ExecuteCommandCommand } from "@aws-sdk/client-ecs";const ecs = new ECSClient({ region: "us-east-1" });const resp = await ecs.send(new ExecuteCommandCommand({ cluster: "prod", task: "arn:aws:ecs:us-east-1:111122223333:task/prod/abc123", container: "api", command: "/bin/sh", interactive: true,}));const session = resp.session;console.log(session?.sessionId);// session.streamUrl + session.tokenValue are handed to the// Session Manager plugin to drive an interactive terminal.
In practice most operators run the equivalent CLI, which wraps this API and launches the plugin for you:
ECS Exec fails silently if either half is missing.
enableExecuteCommand on the task. Set on RunTask, or on a service via CreateService/UpdateService. Enabling it on an existing service requires forceNewDeployment: true so tasks relaunch with the exec agent.
Task-role SSM permissions. The exec agent runs under the task role (not the execution role) and needs ssmmessages:CreateControlChannel, ssmmessages:CreateDataChannel, ssmmessages:OpenControlChannel, and ssmmessages:OpenDataChannel. Without these the agent cannot reach Systems Manager and exec never becomes available.
A task can be RUNNING while its exec agent is still starting. DescribeTasks reports the agent under each container's managedAgents; the ExecuteCommandAgent must be RUNNING before a command will connect.
# --- Python (boto3) ---import boto3ecs = boto3.client("ecs", region_name="us-east-1")task = ecs.describe_tasks(cluster="prod", tasks=[task_arn])["tasks"][0]for c in task["containers"]: for agent in c.get("managedAgents", []): print(c["name"], agent["name"], agent["lastStatus"])
// --- TypeScript (AWS SDK v3) ---import { ECSClient, DescribeTasksCommand } from "@aws-sdk/client-ecs";const ecs = new ECSClient({ region: "us-east-1" });const task = (await ecs.send(new DescribeTasksCommand({ cluster: "prod", tasks: [taskArn!] }))).tasks?.[0];for (const c of task?.containers ?? []) { for (const agent of c.managedAgents ?? []) { console.log(c.name, agent.name, agent.lastStatus); }}
There is no inbound connection to the task. The exec agent inside the container makes an outbound connection to Systems Manager, and your command rides back down that channel. That means no open ports, no SSH keys, and no bastion. Every session can be logged and encrypted: configure the cluster or task's executeCommandConfiguration to send session logs to CloudWatch Logs or S3 and to use a KMS key, giving you an audit trail of who ran what inside production containers.
Forgetting enableExecuteCommand. Exec is off by default. A task started without the flag cannot be exec'd; you must relaunch it with the flag set.
Permissions on the wrong role. The ssmmessages permissions go on the task role, not the execution role. Putting them on the execution role leaves exec broken.
Toggling a service without a new deployment.UpdateService with enableExecuteCommand needs forceNewDeployment: true; existing tasks will not gain the agent otherwise.
Missing the Session Manager plugin. Interactive sessions require the plugin installed locally. Without it the CLI errors even though the API succeeded.
Command before the agent is ready. The ExecuteCommandAgent must be RUNNING in DescribeTasks; running a command too early fails to connect.
Read-only root filesystem. If the container image lacks a shell or runs a read-only root filesystem, /bin/sh may not exist or the agent may fail to start; ensure a shell is present.
CloudWatch Logs via the awslogs driver is the first stop for most debugging - you often do not need a shell at all, just the logs.
A debug sidecar with your tooling baked in, sharing the task, avoids bloating the main image and can be exec'd the same way.
SSM Session Manager on EC2 launch type reaches the host directly; on Fargate there is no host, which is exactly why ECS Exec exists.
Reach for ECS Exec for live, interactive inspection; lean on structured logs and metrics first, and enable exec deliberately with logging on for production containers.
A feature that runs a command inside a running container without SSH, tunneling through AWS Systems Manager. It is how you get a shell into a Fargate task where there is no host to log into.
How do I enable ECS Exec?
Start the task with enableExecuteCommand: true (on RunTask, or on a service via CreateService/UpdateService with a new deployment), and give the task role the required ssmmessages permissions.
Which role needs the SSM permissions?
The task role, not the execution role. It needs ssmmessages:CreateControlChannel, CreateDataChannel, OpenControlChannel, and OpenDataChannel so the exec agent can reach Systems Manager.
Why does my exec command fail to connect?
Common causes are exec not enabled on the task, missing ssmmessages permissions on the task role, the exec agent not yet RUNNING, or no local Session Manager plugin for interactive sessions.
Do I need SSH or open ports for ECS Exec?
No. The exec agent makes an outbound connection to Systems Manager, so there are no inbound ports, no SSH keys, and no bastion. This is what makes it safe to use in production.
Can I use the SDK for an interactive shell?
The SDK ExecuteCommand returns a session (stream URL and token), but driving an interactive terminal needs the Session Manager plugin. Most operators use the aws ecs execute-command CLI, which wraps the API and launches the plugin.
How do I enable exec on an existing service?
Call UpdateService with enableExecuteCommand: true and forceNewDeployment: true. The force flag relaunches tasks so they start with the exec agent; without it, running tasks stay non-exec.
How do I check a task is ready to exec?
Call DescribeTasks and look at each container's managedAgents. The ExecuteCommandAgent must show lastStatusRUNNING before a command will connect.
Can I audit who ran commands?
Yes. Configure the cluster or task executeCommandConfiguration to log sessions to CloudWatch Logs or S3, optionally encrypted with KMS. Every exec session is then recorded for audit.
Why does /bin/sh not work in my container?
The image may not include a shell, or a read-only root filesystem may block the agent. Ensure a shell exists in the image and that the exec agent has the writable paths it needs to start.