DEV Community

Cover image for LocalStack - Mock AWS in local development
Ariful Alam
Ariful Alam

Posted on

LocalStack - Mock AWS in local development

Most of us are familiar with Amazon Web Services (AWS) and have probably used their cloud computing services at some point in our careers. AWS is a go-to solution for many developers when it comes to building and deploying applications on the cloud.

However, working with AWS can come with its challenges, particularly when it comes to testing and development. While AWS offers a wide range of powerful services and tools, navigating through the vast array of options and configuring them correctly can be complex and costly. As AWS provides a pay-as-you-go pricing model, the costs can quickly escalate if resources are not managed effectively while developing and testing. Herein lies the role of LocalStack.

AWS Services

What is LocalStack?

LocalStack is an open-source project that provides a fully functional local AWS cloud stack. It allows developers to test and develop their applications offline without needing an internet connection or any cloud resources. LocalStack allows developers to emulate the behavior of AWS services such as S3, Lambda, DynamoDB, and more, all within a local environment, eliminating the need for an AWS account.

Features and Capabilities

  • Simulation of entire AWS Services: LocalStack enables developers to simulate the entire AWS ecosystem within its local environment.
  • Offline Development: With LocalStack, developers can work offline without relying on an internet connection or AWS account.
  • Cost Savings: By simulating AWS services locally, LocalStack eliminates the costs associated with running resources in the cloud.
  • Easy Setup and Configuration: LocalStack simplifies the setup process with a user-friendly command-line interface (CLI) and APIs.
  • Realistic Testing Scenarios: By emulating the actual behavior of AWS services, LocalStack allows developers to create realistic testing scenarios. This early identification and resolution of potential issues lead to the development of more robust and reliable applications.

Flow

Installation & Setup

There are several ways to install LocalStack on our local machine.

We will use the Docker-Compose method in this article. Docker and docker-compose should be installed on our machine as a prerequisite.

  • Create a docker-compose.yml file and paste the following contents.
    version: "3.8"

    services:
      localstack:
        image: localstack/localstack
        ports:
          - "127.0.0.1:4566:4566"
          - "127.0.0.1:4510-4559:4510-4559"
        environment:
          - DEBUG=${DEBUG-}
          - DOCKER_HOST=unix:///var/run/docker.sock
        volumes:
          - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
          - "/var/run/docker.sock:/var/run/docker.sock"
Enter fullscreen mode Exit fullscreen mode

Limitation

Before continuing the article, we should also know about the limitations of LocalStack. One of the major limitations of LocalStack is that the data are not persistent in the free/community version. Meaning, that every time we stop LocalStack, the data including the configuration we ran via command, the buckets, and the databases, all will be removed and it will start fresh on the next start.

Usage

We are going to create a Lambda function and configure a trigger for Amazon S3 to demonstrate how LocalStack can be used for AWS mocking. When a new image is uploaded to the S3 bucket, the Lambda function should create a thumbnail of that image in the same bucket.

Install AWS CLI

As we are mocking AWS, the official AWS CLI should be installed on our machine. If it’s not installed, follow this guide to install it first. After installing it, we will need to configure it as a first-time user by running aws configure. Set any value as Access Key ID and Secret Access Key and set a Default Region (e.g. ap-northeast-2). If you use your original Access key and secret, it’s okay too, as LocalStack will ignore it ultimately.

How will the AWS CLI communicate with LocalStack?

By default, when we run a command in AWS CLI, it will try to communicate with AWS API. But here, we want to communicate with LocalStack instead AWS API.

When running any AWS command, if we specify the endpoint, it will send the requests to that endpoint instead of the default endpoint.

For example, let’s run a command aws s3api list-buckets --endpoint-url=http://localhost:4566.

Bucket List

It displays the bucket list from LocalStack and does not throw any error even though we used dummy data as the AWS configuration.

How will the AWS SDK communicate with LocalStack?

We can also use LocalStack in our AWS SDKs. We just have to point to the endpoint while initilizting the SDK. Here’s an example of JavaScript aws-sdk:

const AWS = require('aws-sdk');

const s3 = new AWS.S3({
  endpoint: 'http://localhost:4566', // Only needed for localstack
  s3ForcePathStyle: true, // Only needed for localstack
});
Enter fullscreen mode Exit fullscreen mode

Then we can use the s3 object to interact with the S3 service locally through LocalStack.

Create the Lambda function

  • Open a new terminal and create a directory by running mkdir lambda-src.
  • Open the directory by running cd lambda-src.

  • Create a new file package.json and paste the following code:

    {
      "main": "index.js",
      "dependencies": {
        "aws-sdk": "^2.1376.0",
        "sharp": "^0.32.1"
      }
    }
    
  • Run npm install to install the dependencies.

  • Create a new file index.js and paste the following code:

    const AWS = require('aws-sdk');
    const sharp = require('sharp');
    
    const WIDTH = 64;
    
    const s3 = new AWS.S3({
      endpoint: `http://${process.env.LOCALSTACK_HOSTNAME}:${process.env.EDGE_PORT}`, // Only needed for localstack
      s3ForcePathStyle: true, // Only needed for localstack
    });
    
    exports.handler = async (event) => {
      try {
        const srcBucket = event.Records[0].s3.bucket.name;
        const srcKey = decodeURIComponent(
          event.Records[0].s3.object.key.replace(/\+/g, ' ')
        );
    
        if (srcKey.includes(`${WIDTH}x${WIDTH}`)) {
          console.info('Skipping processed image.');
          return;
        }
    
        const destinationKey = srcKey.replace(
          /(\.[^.]*)?$/,
          `-${WIDTH}x${WIDTH}$1`
        ); // output will be name-200x200.png
    
        const typeMatch = srcKey.match(/\.([^.]*)$/);
        if (!typeMatch) {
          console.error('Could not determine the image type.');
          return;
        }
    
        const imageType = typeMatch[1].toLowerCase();
        if (imageType != 'jpg' && imageType != 'png' && imageType != 'jpeg') {
          console.error(`Unsupported image type: ${imageType}`);
          return;
        }
    
        const params = {
          Bucket: srcBucket,
          Key: srcKey,
        };
    
        const originalImage = await s3.getObject(params).promise();
    
        const buffer = await sharp(originalImage.Body).resize(WIDTH).toBuffer();
    
        const destinationParams = {
          Bucket: srcBucket,
          Key: destinationKey,
          Body: buffer,
          ContentType: 'image',
        };
    
        await s3.putObject(destinationParams).promise();
    
        console.log('Successfully resized ' + srcBucket + '/' + srcKey +
            ' and uploaded to ' + srcBucket + '/' + destinationKey
        );
      } catch (error) {
        console.error(error);
        return;
      }
    };
    
  • Then zip the directory. If you have zip installed, you can run:

    zip -r function.zip .
    
  • By running cd .. return to the root directory.

  • Create the Lambda function by running:

    aws --endpoint-url=http://localhost:4566 \
    lambda create-function --function-name CreateThumbnail \
    --zip-file fileb://lambda-src/function.zip --handler index.handler --runtime nodejs16.x \
    --role arn:aws:iam::000000000000:role/lambda-s3-role
    
  • Create a S3 bucket:

    aws --endpoint-url=http://localhost:4566 s3 mb s3://avatar-bucket
    
  • Create a file notification-configuration.json:

    {
      "LambdaFunctionConfigurations": [
        {
          "LambdaFunctionArn": "arn:aws:lambda:ap-northeast-2:000000000000:function:CreateThumbnail",
          "Events": ["s3:ObjectCreated:*"]
        }
      ]
    }
    
  • Configure Amazon S3 to publish events:

    aws --endpoint-url=http://localhost:4566 \
    s3api put-bucket-notification-configuration --bucket avatar-bucket \
    --notification-configuration file://notification-configuration.json
    
  • Copy an image to the directory and upload the image to the S3 bucket:

    aws --endpoint-url=http://localhost:4566 \
    s3api put-object --bucket avatar-bucket \
    --key test-image.png --body=test-image.png
    
  • Check the log:

    aws --endpoint-url=http://localhost:4566 logs tail '/aws/lambda/CreateThumbnail' --follow
    
  • To update the code:

    aws --endpoint-url=http://localhost:4566 lambda update-function-code --function-name CreateThumbnail --zip-file fileb://lambda-src/function.zip
    

Now, a thumbnail will be created in the same bucket whenever a new image is uploaded. Once our development and testing phase is complete, proceed to replicate these actions in the production environment. Do remember to remove the endpoint and s3ForcePathStyle from the code as they are only needed to communicate with LocalStack.

References

Top comments (0)