LocalStack LogoLocalStack Icon

Developing & Debugging AWS ECS Tasks Locally with LocalStack & VS Code

Learn how to develop and debug AWS ECS tasks locally using LocalStack with Docker bind mounts for code mounting, VS Code for step-through debugging, and hot-reloading to instantly apply changes without rebuilding container images.

Developing & Debugging AWS ECS Tasks Locally with LocalStack & VS Code

Introduction

Amazon Elastic Container Service (ECS) allows developers to deploy and scale containerized applications on AWS. However, testing and debugging ECS tasks often involves rebuilding Docker images, redeploying to the cloud, and waiting for updates – a slow, inefficient cycle that stifles rapid iteration.

LocalStack solves this by emulating ECS locally. It enables hot-reloading to instantly apply code changes without rebuilds, and local debugging to set breakpoints and inspect tasks in real time. This enables you to eliminate the cloud development tax, allowing you to validate logic, fix errors, and refine behavior faster – all within a local development loop.

In this tutorial, we’ll walk through a streamlined ECS development workflow with LocalStack by setting up a sample Node.js application using AWS CDK. The CDK stack will deploy a Node.js containerized app locally by:

  • Creating a local VPC and an ECS Cluster
  • Building and pushing the application image to a local ECR repository
  • Adding Task and Container Definitions for the local ECS tasks, including volume mounts
  • Running the container on Fargate, distributing traffic using an Elastic Load Balancer (ELB)

After deploying the CDK stack locally, we’ll configure code mounting, test hot-reloading, and set up VS Code to attach a debugger to the running ECS task.

Architecture for Node.js app on ECS with Elastic Load Balancer

Key Concepts

Without local testing tools, development cycles become inefficient. Small code changes require lengthy rebuilds, and debugging relies on cumbersome tools like ECS Exec or the ECS container agent rather than using IDE-integrated debuggers.

LocalStack solves these problems by enabling local ECS development and debugging through two key methods: Docker bind mounts for code synchronization and environment variables for exposing debugger ports

ECS Code Mounting with Bind Mounts

ECS code mounting in LocalStack uses Docker bind mounts to synchronize your local development environment with locally running ECS tasks in real-time. Bind mounts allow you to directly mount a directory or file from your local system into a container. LocalStack emulates AWS ECS locally, enabling you to define tasks with bind mounts just like in AWS but with faster iteration.

To set this up, you can configure a volume in the ECS task definition and specify the container mount point to map the host directory to the container’s filesystem. As we’ll see in the tutorial when LocalStack deploys the ECS task, it translates this configuration and uses Docker bind mounts to link the specified host directory (e.g., ./src/app) to the container’s path (e.g., /app).

Docker bind mounts

To reflect changes instantly, enable auto-reloading on your application server (like --watch for Node.js) so that updates on the host system are applied within the running container without rebuilding the Docker image.

Remote Debugging Port Configuration

Similarly, you can enable a remote debugging port for your ECS tasks by setting the environment variable ECS_DOCKER_FLAGS. This variable allows you to pass additional flags directly to the docker run command that LocalStack uses to start your container.

For example, to expose the Node.js debugger on port 9229 and map it to the same port on the host, you can set: ECS_DOCKER_FLAGS="-p 9229:9229". You might also need to configure your application runtime to listen for debug connections, often via environment variables passed within the flags (more on this in the tutorial).

By using LocalStack’s ECS emulation with bind mounts and remote debugging, LocalStack enables real-time development and debugging for faster iterations. Now, let’s put this into practice by setting up our application and walking through the process.

Prerequisites

Step 1: Install the dependencies

To begin, clone the sample repository from GitHub:

Terminal window
git clone https://github.com/localstack-samples/sample-cdk-ecs-elb.git
cd sample-cdk-ecs-elb

After cloning, navigate to the CDK infrastructure directory (iac/awscdk) and install the Node dependencies:

Terminal window
cd iac/awscdk
npm install

Step 2: Deploy the infrastructure

We’ll use cdklocal, a wrapper for using AWS CDK with LocalStack.

2.1: Start LocalStack

Before deploying, make sure LocalStack is up and running:

Terminal window
# Ensure your Auth Token to use Pro features (like ECS)
localstack auth set-token <YOUR_LOCALSTACK_AUTH_TOKEN>
localstack start -d

2.2: Bootstrap LocalStack

Next, ensure your LocalStack environment is bootstrapped:

Terminal window
# Run this command in the iac/awscdk directory
cdklocal bootstrap

2.3: Deploy the stack

Next, deploy the CDK stack:

Terminal window
# Still in the iac/awscdk directory
cdklocal deploy

Wait for the deployment to complete. You should see an output similar to this (including the URL for the load balancer):

Terminal window
Do you wish to deploy these changes (y/n)? y
RepoStack: deploying... [1/1]
RepoStack: creating CloudFormation changeset...
RepoStack
Deployment time: 15.14s
Outputs:
RepoStack.MyFargateServiceLoadBalancerDNS704F6391 = lb-2e7934b9.elb.localhost.localstack.cloud
RepoStack.MyFargateServiceServiceURL4CF8398A = http://lb-2e7934b9.elb.localhost.localstack.cloud
RepoStack.localstackserviceslb = lb-2e7934b9.elb.localhost.localstack.cloud:4566
RepoStack.serviceslb = lb-2e7934b9.elb.localhost.localstack.cloud
Stack ARN:
arn:aws:cloudformation:us-east-1:000000000000:stack/RepoStack/880f3cce
Total time: 17.04s

2.4: Verify the deployment

Test the deployed service using the output URL (adjust since yours will be different):

Terminal window
curl lb-2e7934b9.elb.localhost.localstack.cloud:4566

This command will invoke its simple GET endpoint.

The output should be:

Terminal window
{"message":"Hi Localstack!"}

Step 3: Configure & Test ECS Code Mounting

The project structure is organized as follows:

  • src/app: Contains the Node.js application code (server.js) and Dockerfile.
  • iac/awscdk: Contains the CDK code defining the infrastructure, including the volume mount configuration.

3.1: Inspect the volume mount configuration

Open iac/awscdk/lib/repo-stack.ts, around Line 30 to find the volume definition:

// Defines a volume named 'local-src' that points to the host machine's source code directory
taskDefinition.addVolume({
name: 'local-src',
host: {
sourcePath: path.join(__dirname, '../../../src/app') // Host machine path relative to CDK code
}
});

This is the directory we want to mount into the ECS task container.

Around Line 49, you’ll find the container mount point definition:

// Mounts the 'local-src' volume to the '/app' directory inside the container
container.addMountPoints({
containerPath: '/app', // Path inside the container
sourceVolume: 'local-src', // Reference to the volume defined above
readOnly: false // Allow write access from within the container if needed
});

When deployed via cdklocal, LocalStack interprets this and uses a Docker bind mount to link sample-cdk-ecs-elb/src/app on your host machine to /app inside the running container. Changes made to files in src/app on the host are immediately visible in the container’s /app directory.

3.2: Test the code mounting

Let’s test it out. Navigate to src/app/server.js in your editor, find Line 5, and change the message:

res.end(JSON.stringify({message: "Hi, from an ECS Task running in LocalStack"}))

Save the file. Now, run the curl command again. You should immediately see the updated response, demonstrating hot-reloading via the bind mount:

Terminal window
{"message":"Hi, from an ECS Task running in LocalStack"}

Note: The sample application is using Node.js watch mode (--watch) which monitors the mounted /app directory for file changes and automatically restarts the Node.js server.

This set up is now using LocalStack with Docker bind mounts for fast local development.

Step 4: Set Up VS Code for remote debugging

Let’s set up VS Code configurations to establish a quick feedback loop for debugging.

4.1: Re-create the LocalStack environment

First, stop your running LocalStack container with the localstack stop command. Then, restart LocalStack, setting the ECS_DOCKER_FLAGS environment variable to configure port mapping and Node.js debug options:

Terminal window
ECS_DOCKER_FLAGS="-e NODE_OPTIONS=--inspect-brk=0.0.0.0:9229 -p 9229:9229" localstack start -d

-p 9229:9229: Maps container port 9229 to host port 9229 for the debugger • -e NODE_OPTIONS=--inspect-brk=0.0.0.0:9229: Enables Node.js debugging on all interfaces

After restarting LocalStack with the flags, re-deploy the stack to ensure the new container settings are applied:

Terminal window
# In the iac/awscdk directory
cdklocal bootstrap
cdklocal deploy

Note the new load balancer URL, as it will likely have changed.

4.2: Create VS Code configurations

We can now add VS Code configurations for debugging. If the .vscode directory doesn’t exist, create one in the root of your sample-cdk-ecs-elb project.

Add the following configuration to .vscode/tasks.json:

{
"version": "2.0.0",
"tasks": [
{
"label": "Wait Remote Debugger Server",
"type": "shell",
"command": "while [[ -z $(docker ps | grep :9229) ]]; do sleep 1; done; sleep 1;"
}
]
}

Create .vscode/launch.json to define how VS Code should connect to the remote Node.js app:

{
"version": "0.2.0",
"configurations": [
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}",
"name": "Attach to Remote Node.js",
"port": 9229,
"remoteRoot": "/app",
"request": "attach",
"type": "node",
"preLaunchTask": "Wait Remote Debugger Server"
}
]
}

Step 5: Test ECS Remote Debugging

For the debugging process, you can set breakpoints in the Node.js application code. Breakpoints pause code execution on a specific line to inspect it. To attach the debugger and test the setup:

  1. Navigate to src/app/server.js.
  2. Click in the gutter next to Line 5 (or another line inside the request handler) to set a breakpoint (a red dot should appear).
  3. Go to the “Run and Debug” view in VS Code (usually the icon with a play button and a bug).
  4. Select “Attach to Remote Node.js (ECS)” from the dropdown menu at the top and click the green “Start Debugging” play button.
  5. Once attached (the status bar might turn orange), invoke the service endpoint. Use awslocal to get the current DNS name if needed:
    Terminal window
    DNS_NAME=$(awslocal elbv2 describe-load-balancers --query 'LoadBalancers[0].DNSName' --output text)
    curl http://$DNS_NAME:4566

Execution should pause at the breakpoint you set:

Remote debugging in action

You can now:

  • Inspect variables in the “Variables” pane.
  • Add expressions to the “Watch” pane.
  • Use the debug toolbar (at the top) to step over, step into, step out, or continue execution.

After you continue execution (press F5 or the Continue button), the curl command in your terminal will complete and show the response:

Output of the cURL command

You can now leverage code mounting to make changes to your Node.js app without rebuilding or redeploying the Docker image, allowing faster debugging and testing.

Summary

This tutorial demonstrates how LocalStack’s Developer Experience (DevEx) features allow you to leverage Docker bind mounts for instant code updates and VS Code’s remote debugging capabilities to debug ECS apps locally without time-consuming rebuilds and deployments.

Now that you’ve seen how LocalStack streamlines ECS development, you can:

  • Apply this approach to more complex microservices architectures
  • Extend debugging capabilities to other AWS services (e.g., Lambda)
  • Write integration tests that leverage the local debugging environment
  • Incorporate LocalStack into your CI pipelines for pre-deployment testing

Try our sample repository to experience this streamlined workflow firsthand. Whether you’re refining task definitions, fixing bugs, or experimenting with new logic, LocalStack empowers you to embrace trial and error without cloud costs or delays.


Harsh Mishra
Harsh Mishra
Engineer at LocalStack
Harsh Mishra is an Engineer at LocalStack and AWS Community Builder. Harsh has previously worked at HackerRank, Red Hat, and Quansight, and specialized in DevOps, Platform Engineering, and CI/CD pipelines.