Note: all code discussed in this article is available here.
If you work in modern web development, chances are you’re probably working on Amazon Web Services (AWS). Although there are other competitors such as Azure, many businesses use AWS as their cloud provider. Its services are well-developed and robust, so it’s no wonder that many choose to use it.
If you’re not a company but rather an individual developer, practicing with AWS can be somewhat awkward. There is the free tier, the 12 free months of AWS that you are given upon joining, however a) that runs out and b) if you’re not careful you can leak your key, and end up on the hook for computing resources created by bad actors.
Although I work extensively with AWS in my day-to-day job, I’ve often been quite cagey about using my AWS account. I’m long beyond eligibility for free tier (I’ve had my own personal AWS account for a while), and although I’ve occasionally messed around on personal projects on there, I’m always nervous of falling foul of this…
Messing around too much with AWS, or negligence can soon lead to rather steep costs, and indeed many organisations developing on AWS like to place emphasis on cost-cutting.
Not wanting to run the risk of huge AWS bills, but also wanting to experiment with it, I searched around for an AWS simulator, and I might have found the answer: Localstack.
What is Localstack?
Localstack is a tool that allows developers to run AWS services locally, without needing an AWS account or an internet connection. It’s a fully functional clone of AWS that runs in a Docker container, and supports most of the AWS APIs and features that many developers will be familiar with, such as S3, Lambda, DynamoDB, SQS etc.
Needless to say, the thought of being able to mess around with AWS without having to run the risk of huge costs or leaking my AWS key in a thoughtless git commit was very appealing, and so I decided to set up a test project and investigate further. Let’s look at it!
Testing Out Localstack
There are a few prerequisites here. If you’re reading this, I’m going to assume that you’re familiar with AWS and the AWS CDK. I’m also going to assume that you’ve installed the CDK toolkit in order to be able to compile and deploy CDK code, and the AWS CLI to work with AWS locally.
Assuming this, create a folder called localstack-test, and then run cdk init app –language typescript to create an empty typescript CDK project.
With that out of the way, there are some additional steps you need to do to make your machine work with Localstack:
- You need to install awscli-local, which is Localstack’s wrapper around the AWS CLI. This can be installed by pip or pipx (I prefer the latter)
- You also need to install aws-cdk-local, which is Localstack’s CDK wrapper: npm install –save-dev aws-cdk-local
Once these are out of the way, go to localstack-test.ts in lib, and paste in the following:
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as s3 from 'aws-cdk-lib/aws-s3';
export class LocalstackTestStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// nothing special - just create a test queue and bucket
const stackTestQueue = new sqs.Queue(this, 'LocalstackTest1Queue', {
visibilityTimeout: cdk.Duration.seconds(300)
});
const stackTestBucket = new s3.Bucket(this, 'stackTestBucket');
}
}
and then in bin, in localstack-test.ts paste:
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { LocalstackTestStack } from '../lib/localstack-teststack';
const app = new cdk.App();
new LocalstackTestStack(app, 'LocalstackTestStack', {
// if you're wondering why I'm using LegacyStackSynthesizer
// instead of bootstrapping: this is how I do it at work
// and I'm lazy 😛
synthesizer: new cdk.LegacyStackSynthesizer()
});
This is very simple code that will just create a stack containing an SQS queue and a bucket: we’re not wanting to do anything extravagant, we just want to see if it will work!
Next, create a docker-compose.yml and paste in the following:
version: "3.8"
services:
localstack:
container_name: "${LOCALSTACK_DOCKER_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:
# LocalStack configuration: https://docs.localstack.cloud/references/configuration/
- DEBUG=${DEBUG:-0}
volumes:
- "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
Run docker-compose up and you should see it spin up a Localstack container.
To deploy our stack to Localstack, we need to go to package.json, and under scripts add the following entry:
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk",
# add cdklocal!
"local": "cdklocal"
}
We’re now set up to deploy to our Localstack instance! Run curl -v http://localhost:4566 and if all is well you should get a 200, and something like this:
(base) gaz@gojira:$ curl -v http://localhost:4566 * Trying 127.0.0.1:4566... * Connected to localhost (127.0.0.1) port 4566 (#0) > GET / HTTP/1.1 > Host: localhost:4566 > User-Agent: curl/7.84.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 < Content-Type: text/plain; charset=utf-8 < Connection: close < Content-Length: 0 < date: Tue, 06 Feb 2024 19:45:40 GMT < server: hypercorn-h11 < * Closing connection 0
Localstack is up and running!
Run npm run local deploy and you will see it seemingly go through the motions of deploying to AWS – however it’s deploying to Localstack instead.
Once it’s finished, run awslocal s3 ls, and you should see something like this:
(base) gaz@gojira:$ awslocal s3 ls 2024-02-06 20:02:28 localstackteststack-stacktestbucketb99d4ef0-04670a8c
and running awslocal sqs list-queues should yield something like this:
(base) gaz@gojira:$ awslocal sqs list-queues
{
"QueueUrls": [
"http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/LocalstackTestStack-LocalstackTestQueue8672-29816a24"
]
}
Et voilà! You’ve just deployed an AWS stack to a local instance rather than Amazon’s cloud!
If it were AWS, we’d probably want to tear these down in order to avoid any costs, so running npm run local destroy and selecting yes will tear down the stack.
Sounds great! What’s the catch?
Of course, this is a really simple example, but I hope that it’s demonstrated Localstack and how, using it, you can deploy AWS resources locally. I’m still playing around with it, but I’m excited.
This example is using the free community edition of Localstack, which allows you to simulate basic services such as S3 and SQS. However, Localstack restricts some of its more advanced features (for instance, local ECR repositories and Cognito) to Localstack Pro.
For an individual developer it costs $35, and for commercial use a license is about $70 per seat. I’ve only been using the Localstack community edition so far, but I like what I see, and I’m thinking I might well upgrade to the full edition in order to be able to test and debug more advanced services. Now that I don’t have to worry about accidentally causing myself to have to take out a second mortgage, I’m starting to think about the possibilities…