How to debug AWS ECS Tasks locally using LocalStack and VS Code

Learn how to deploy and debug ECS tasks locally with LocalStack by attaching a remote debugger in VS Code to facilitate local development & testing!

How to debug AWS ECS Tasks locally using LocalStack and VS Code

LocalStack offers some important features that can improve the developer experience of building applications on ECS, including hot-reloading and debugging for compute infrastructure like Lambda and ECS.

LocalStack allows you to debug your application code in ECS tasks by setting breakpoints and improving your development and testing process without the requirement to deploy to the real cloud. This post will show you how to set up a local ECS cluster and task, open a remote debugging port on the LocalStack container, configure VS Code for debugging, and run the debugger.

Prerequisites

Node.js app on ECS with Elastic Load Balancer

The sample application uses AWS CDK to deploy a Node.js containerized app on AWS Fargate within an ECS cluster. The CDK stack:

  • Creates a local VPC and an ECS Cluster.

  • Builds and pushes the Docker image to a local ECR repository.

  • Adds task and container Definition for the local ECS tasks.

  • Runs containers on Fargate, distributing traffic using ELB.

After deploying the CDK stack, you will create a VS Code task & launch configuration and attach the debugger to the ECS task.

Start your LocalStack container

Launch the LocalStack container on your local machine using the specified command:

export LOCALSTACK_AUTH_TOKEN=<your-auth-token>
ECS_DOCKER_FLAGS="-e NODE_OPTIONS=--inspect-brk=0.0.0.0:9229 -p 9229:9229" \
localstack start

The configuration variable ECS_DOCKER_FLAGS in the command mentioned above is used to pass additional flags to Docker when creating ECS task containers. The option is used to enable a remote debugging port for your ECS tasks. This exposes the debugger on a random port on the host machine, allowing you to remotely attach your debugger to the ECS tasks.

Alternatively, you can utilize the Docker Compose configuration provided in the repository to start the LocalStack container with the following command:

cd devops-tooling && docker compose -p ecslb up

Install the dependencies

To begin, fork the LocalStack sample repository on GitHub using this command:

git clone git@github.com:localstack-samples/sample-cdk-ecs-elb.git

After cloning the repository, navigate to the iac/awscdk directory and install all the required dependencies with the following command:

cd iac/awscdk
npm install

Deploy the application locally

To deploy the application, you should use cdklocal, which is a wrapper script for utilizing the AWS Cloud Development Kit (CDK) with local APIs provided by LocalStack.

Start by ensuring that each AWS environment you plan to deploy resources to is bootstrapped. Run the following command in the iac/awscdk directory:

cdklocal bootstrap

Next, you can deploy the CDK stack using this command in the iac/awscdk directory:

cdklocal deploy

After a successful deployment, you should see output similar to the following:

✅  RepoStack

✨  Deployment time: 20.15s

Outputs:
RepoStack.MyFargateServiceLoadBalancerDNS704F6391 = lb-bf1b158e.elb.localhost.localstack.cloud
RepoStack.MyFargateServiceServiceURL4CF8398A = http://lb-bf1b158e.elb.localhost.localstack.cloud
RepoStack.localstackserviceslb = lb-bf1b158e.elb.localhost.localstack.cloud:4566
RepoStack.serviceslb = lb-bf1b158e.elb.localhost.localstack.cloud
Stack ARN:
arn:aws:cloudformation:us-east-1:000000000000:stack/RepoStack/77d40ed8

✨  Total time: 22.55s

You can now use the URLs provided to send requests to the Node.js application running in the ECS task locally. To inspect the ELB endpoint further, if you have awslocal installed, run the following command:

awslocal elbv2 describe-load-balancers --query 'LoadBalancers[0].DNSName' --output text

The output should be:

lb-bf1b158e.elb.localhost.localstack.cloud

Configure VS Code for remote debugging

While starting LocalStack, you configured ECS_DOCKER_FLAGS to enable the required configuration for remote debugging. You can now add a VS Code task to wait for the remote debugging server. Create a tasks.json file in the .vscode directory of the project and add the following:

{
    "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;"
        }
    ]
}

Next, you can define how VS Code should connect to the remote Node.js application. Create a new launch.json file in the .vscode directory of the project and add the following:

{
    "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"
        }
    ]
}

Run the debugging process

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

  • Click on Run and Debug in your VS Code session.

  • Select the Attach to Remote Node.js configuration and click Start Debugging.

  • Navigate to src/app/server.js in your VS Code and set breakpoints (for example, on line 5).

  • Open your terminal and execute the following command to trigger the code where you set the breakpoint:

      curl "lb-bf1b158e.elb.localhost.localstack.cloud:4566"
    

    Replace the endpoint URL with the appropriate URL in your setup.

You will now see the remote debugging in action on your VS Code:

You can now inspect the values of any identifiers within the current scope using the Variables and Watch pane in VS Code. Additionally, use the debug toolbar at the top of the editor to step through the code, continue execution, or manage breakpoints during debugging.

After the debugging process is completed, the command you ran previously will produce the following output:

Conclusion

That brings us to the end of our tour of how you can debug ECS tasks locally with LocalStack. You can mount code from the host filesystem into the ECS container for hot reloading, enabling quick debugging and testing without needing to rebuild and redeploy the Docker image each time. LocalStack not only lets you deploy and test your infrastructure but also offers various developer experience (DevEx) features, allowing you to develop your application without interacting with the actual cloud throughout your software development lifecycle.