Getting Started with LocalStack

Getting Started with LocalStack

LocalStack
Docker
Node.js
AWS
2022-02-02

Introduction

In this post, I’ll walk you through how I use LocalStack—an incredibly handy cloud service emulator—to develop and test my AWS-dependent applications entirely on my local machine. LocalStack provides a quick way to replicate AWS services such as S3, SQS, DynamoDB, and more without incurring the cost or hassle of interacting with live AWS resources. This approach not only saves money but makes for a faster, more efficient development cycle.

Background

What is LocalStack exactly? At its core, LocalStack spins up a local environment that emulates a wide range of AWS services inside a Docker container. This means I can develop, debug, and test my AWS-based applications quickly, without an internet connection, and with minimal risk of accidentally incurring usage fees. It’s also perfect for CI/CD pipelines, letting me run integration tests against AWS-like services reliably. The power of LocalStack lies in its simplicity and flexibility, acting as a reliable stand-in for major AWS components during development.

Problem Statement

Traditional AWS workflows require uploading code to the cloud for testing or provisioning ephemeral environments—often driving up costs or complicating local debugging tasks. Developers face issues like intermittent network connectivity, slow development loops, or AWS usage fees for testing prototypes. LocalStack addresses these hurdles by creating a frictionless environment that is entirely local, consistent across development setups, and fast to iterate upon.

Detailed Discussion

Let’s get hands-on with LocalStack by walking step-by-step through setting it up, spinning up a Node.js application, and testing real AWS-like capabilities (S3, SQS) without ever leaving your local machine.

I like to use Docker Compose for convenience, so the first step is creating a docker-compose.yml file at the root of my project:

version: '3.8' services: localstack: container_name: "localstack_main" image: localstack/localstack ports: - "127.0.0.1:4566:4566" # LocalStack Gateway - "127.0.0.1:4510-4559:4510-4559" # external services port range environment: - DEBUG= - DOCKER_HOST=unix:///var/run/docker.sock volumes: - "./volume:/var/lib/localstack" - "/var/run/docker.sock:/var/run/docker.sock"

Once the file is in place, I can spin up LocalStack with a simple command:

docker-compose up -d

Next, I'll set up a Node.js project to interact with the AWS services emulated by LocalStack. In my project folder, I initialize a new Node.js project and install the necessary dependencies:

npm init -y npm install aws-sdk dotenv

I also create a .env file in my project root to hold my LocalStack credentials and endpoint configuration:

AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_REGION=us-east-1 AWS_ENDPOINT=http://localhost:4566

To demonstrate how straightforward this is, I’ll create a script called s3-example.js that creates an S3 bucket, uploads a file, and then retrieves it:

require('dotenv').config(); const AWS = require('aws-sdk'); // Configure AWS SDK to use LocalStack const s3 = new AWS.S3({ endpoint: process.env.AWS_ENDPOINT, s3ForcePathStyle: true, }); async function s3Example() { const bucketName = 'my-test-bucket'; const fileName = 'test-file.txt'; const fileContent = 'Hello, LocalStack!'; try { // Create a bucket await s3.createBucket({ Bucket: bucketName }).promise(); console.log(`Bucket created: ${bucketName}`); // Upload a file await s3.putObject({ Bucket: bucketName, Key: fileName, Body: fileContent, }).promise(); console.log(`File uploaded: ${fileName}`); // List objects in the bucket const listResult = await s3.listObjects({ Bucket: bucketName }).promise(); console.log('Objects in bucket:', listResult.Contents); // Get the uploaded file const getResult = await s3.getObject({ Bucket: bucketName, Key: fileName }).promise(); console.log('File content:', getResult.Body.toString()); } catch (error) { console.error('Error:', error); } } s3Example();

Running node s3-example.js should produce output showing that the bucket was created, a file was uploaded, and the file’s content was retrieved successfully.

Next up, I’ll test the SQS service using LocalStack. I create another file called sqs-example.js:

require('dotenv').config(); const AWS = require('aws-sdk'); // Configure AWS SDK to use LocalStack const sqs = new AWS.SQS({ endpoint: process.env.AWS_ENDPOINT, }); async function sqsExample() { const queueName = 'my-test-queue'; try { // Create a queue const createQueueResult = await sqs.createQueue({ QueueName: queueName }).promise(); const queueUrl = createQueueResult.QueueUrl; console.log(`Queue created: ${queueUrl}`); // Send a message const sendResult = await sqs.sendMessage({ QueueUrl: queueUrl, MessageBody: 'Hello, LocalStack SQS!', }).promise(); console.log(`Message sent: ${sendResult.MessageId}`); // Receive messages const receiveResult = await sqs.receiveMessage({ QueueUrl: queueUrl, MaxNumberOfMessages: 1, }).promise(); if (receiveResult.Messages && receiveResult.Messages.length > 0) { console.log('Received message:', receiveResult.Messages[0].Body); // Delete the message await sqs.deleteMessage({ QueueUrl: queueUrl, ReceiptHandle: receiveResult.Messages[0].ReceiptHandle, }).promise(); console.log('Message deleted'); } else { console.log('No messages received'); } } catch (error) { console.error('Error:', error); } } sqsExample();

Running node sqs-example.js will create a queue, send a message, receive the message, and finally delete it.

If you want to package everything into its own Docker container, you can create a Dockerfile at the root of your project:

FROM node:14 WORKDIR /usr/src/app COPY package*.json ./ RUN npm install COPY . . CMD ["node", "s3-example.js"]

You can build and run this Docker image locally, ensuring it communicates with LocalStack:

docker build -t my-localstack-app . docker run --network host -e AWS_ENDPOINT=http://localhost:4566 my-localstack-app

To manage both LocalStack and your Node.js application together, I usually update the docker-compose.yml to include my app as a separate service:

version: '3.8' services: localstack: container_name: "localstack_main" image: localstack/localstack ports: - "127.0.0.1:4566:4566" - "127.0.0.1:4510-4559:4510-4559" environment: - DEBUG= - DOCKER_HOST=unix:///var/run/docker.sock volumes: - "./volume:/var/lib/localstack" - "/var/run/docker.sock:/var/run/docker.sock" app: build: . environment: - AWS_ENDPOINT=http://localstack:4566 depends_on: - localstack

Then I can start both with a single command:

docker-compose up --build

Best Practices

LocalStack offers a powerful emulation experience, but there are some tips I find particularly helpful:

  • Use Unique Resource Names: LocalStack doesn’t require globally unique resource names like AWS does. However, using unique names can help avoid conflicts if you’re running multiple tests or containers concurrently.
  • Verify Service Support: LocalStack supports a majority of AWS services, but not every feature or service is fully implemented. Always check the official feature coverage if you need something beyond the basics.
  • Clean Up Resources Regularly: Deleting buckets, queues, or other resources after you’re done can keep your environment uncluttered and reduce confusion.
  • Watch Out for AWS Regions: While LocalStack defaults to us-east-1, ensure your code and SDK configurations match the region you intend to use to avoid unexpected errors.
  • Network Configuration: When containerizing your application, ensure it can communicate with the LocalStack container. Passing --network host is a quick fix on some systems, but you may need a dedicated Docker network for more complex setups.

Conclusion

By using LocalStack, I can develop and test AWS features in a frictionless local environment. Throughout this post, I showed you how to set up LocalStack, configure a Node.js application to interact with S3 and SQS, and finally containerize everything using Docker and Docker Compose.

If you find yourself frequently creating and tearing down AWS resources just to test your code—or worrying about racking up unexpected AWS charges—LocalStack is a terrific solution. I hope this guide helps you get started. Feel free to explore other AWS services (like DynamoDB, Lambda, or API Gateway) within LocalStack to further streamline your development workflow.

Happy coding!
– Nate

Further Reading

If you’d like to dive even deeper, here are a few resources to expand your knowledge on LocalStack and AWS development: