Is it possible to do cloud development without the cloud?

Customers often ask me if they could develop and test AWS applications locally on their machines before uploading the code to AWS cloud.

It’s a valid question but the answer is not that straightforward.

As a developer, I want feedback cycle to be as fast as possible. When I implement something I want to know immediately if I made an error or if things are working as intended. Ideally, all the tools I would need for developing an application should already be installed on my laptop.

These kind of habits are understandably difficult to support when it comes to cloud development.

I believe this is the main reason why container-based development (Kubernetes and friends) is still dominant and preferred way of deploying applications to the cloud. With containers, developers can have the best of both worlds – local dev/test environment and cloud deployment.

However, even with container-based approach there is still a need to use some of cloud services. Containerized applications can’t live in isolation and are dependent on cloud services like authentication, load balancing, managed databases, managed streaming solutions etc.

That brings us back to the original question: can we develop our applications locally without the cloud?

So far I have seen three types of attempts to resolve this issue:

  1. AWS service mocking tools
  2. AWS serverless frameworks
  3. Mimicking functionality of AWS cloud services locally

Service mocking tools such as Moto and AWS Client Mock provide libraries in Python and Javascript that you can use to test your commands against mocked API service calls. I personally haven’t found much value in using such tools. It requires a lot of effort to write tests with mocked services that support only a limited set of AWS functionality. At the end the I got no confidence in my tests and I spent quite some time writing them.

AWS serverless frameworks such as AWS SAM and Amplify CLI provide a limited support for local development and testing of AWS services, mainly supporting API Gateway and Lambda. If you use these frameworks then it is possible to test few (serverless) services locally, but not all of the available AWS services. I personally am not big fan of AWS SAM / Amplify CLI as I don’t think they are good fit for enterprise-grade application development and due to limited service support they are not good fit for local development .

That brings us to the last option – mimicking functionality of AWS cloud services locally. This is as hard to do as it sounds. Basically, all AWS has developed in the cloud you should redevelop locally and provide that through a local endpoint using Docker usually.

Is there such thing?

The only one I have found so far is Localstack.

Localstack claims: “LocalStack provides an easy-to-use test/mocking framework for developing Cloud applications. It spins up a testing environment on your local machine that provides the same functionality and APIs as the real AWS cloud environment.

That is a very ambitious statement! AWS has tens of thousands engineers that have been developing services for the last 15 years. Localstack has 400+ open source contributors and has been active since 2016.

So, let’s see what Localstack is and if it is worth using it.

Localstack comes with three licenses, Community which is free, Pro which is $15/user/month and Enterprise with unknown price tag. Pro license can be obtained for free for 14 days trial period. I signed up for the trial period of the Pro version.

Getting started with Localstack is quite smooth. Install Localstack CLI:

python3 -m pip install localstack

There is also Localstack Cockpit that can be separately downloaded and installed as a desktop application.

I find Cockpit to be quite useful for defining Environment Variables and for starting/stopping the Localstack engine. Also, there is a link to Web App that opens Localstack Dashboard web application.

I wish I could say that Dashboard web app is useful, but it’s not. It doesn’t seem to work properly. Maybe it has just been abandoned by the Localstack team. It shows resources that don’t exist anymore and fails to show newly created resources. Nice idea and would definitely be super useful to work with, only if it worked as intended.

Another nice thing about Localstack is that it supports integration with Infrastructure as Code frameworks such as CDK, Pulumi and Terraform.

I use CDK so I installed integration with it:

npm install -g aws-cdk-local

This gives me bash command cdklocal that I can use just the way I would with the AWS CDK itself.

First you need to bootstrap your local environment:

cdklocal bootstrap aws://000000000000/us-east-1

Account ID should be all zeros and you can choose the region as you wish. This is not documented by the way. Took some research to find out.

That actually brings me to another topic: documentation. Localstack is very poorly documented. There is a dedicated Docs page but it’s very minimal and explains only how to get started. Anything more advanced is not documented.

Anyway, now that the environment is bootstrapped I can create my CDK application:

cdklocal init MyApp --language=javascript

Localstack CDK supports only JavaScript (wish it was documented somewhere). I am ok with using Javascript but I wonder why Typescript hasn’t been selected as that is the base language for AWS CDK.

Ok, now that I have set up everything I need it’s time to start coding. I decided to start with Cognito service first. Cognito is widely used AWS authentication service that many of customers are using. For me it is an essential service for most of my work so I wanted to see how it works with Localstack.

On the Localstack Feature Coverage page it says that Cognito has Tier 1 coverage for users and user pools. Tier 1 (⭐⭐⭐⭐) means that the feature is fully supported by LocalStack maintainers and that feature is guaranteed to pass all or the majority of tests.

I created Cognito pool in CDK and ran cdklocal deploy. That worked. I could see my Cognito pool both in the web app and when using Localstack CLI:

awslocal cognito-idp list-user-pools

Then I wanted to add users via CDK. That didn’t work. I had to manually add users.

The next step was creating Cognito Client App. Also didn’t work through CDK so I used Localstack CLI to do so:

awslocal cognito-idp create-user-pool-client --user-pool-id us-east-1_58657fe4f913439ef63296204d92 --client-name test-client

Ok, finally I have all I need to check the user sign-in process. At this stage Localstack Web App is totally lost. Not showing Cognito Client App and showing user ID instead of user name. Doesn’t matter, it’s all there. I realized I have to rely on Localstack CLI only and forget about the Localstack Web App.

I coded front-end Javascript login to test the sign in process.

var cognitoidentity = new AWS.CognitoIdentityServiceProvider({
            region: 'us-east-1', 
            endpoint: 'http://localhost:4566', 
            apiVersion: '2016-04-18'});

          var params = {
            AuthFlow: 'USER_PASSWORD_AUTH', 
            ClientId: '6cectf67766383kocllvrch', 
            AuthParameters: {
              'USERNAME': 'myemail@gmail.com',
              'PASSWORD': 'MyPassword2!'

            }
          };
          cognitoidentity.initiateAuth(params, function(err, data) {
            if (err) console.log(err, err.stack); // an error occurred
            else {
              // successful response
              // proceed with cognitoidentity.respondToAuthChallenge...

What I really like about Localstack is the easy AWS SDK integration. It requires just changing the endpoint (https://localhost:4566) and all the requests are redirected to Localstack instead of AWS cloud.

The code above was successful in executing cognitoidentity.initiateAuth call. However, the response from Localstack Cognito service was wrong. Localstack immidiatelly returned Authentication Token.

When using Cognito USER_PASSWORD_AUTH authentication flow and if the user has FORCE_CHANGE_PASSWORD status in Cognito, then Cognito sends us to the next authentication challenge. Cognito first returns Session Token that is used to call cognitoidentity.respondToAuthChallenge. If that subsequent call is successful only then we are allowed to receive Authentication Token.

This is not exact implementa of Cognito locally. Ok, I modified my sign-in code to skip the second authentication step. I then implemented changePassword call to remove that FORCE_CHANGE_PASSWORD status in Cognito. I was managed to execute the call and the password was successfully changed but the status hasn’t been updated to Confirmed. I would need to change that status manually to Confirmed, otherwise every login would force me to change password again.

This is where I decided to stop testing Localstack any further. These are already workarounds that I was not willing to make as it would make big drift to the real cloud deployment later on. For the service that receives Tier 1 support from Localstack I expected this flow to be working correctly.

Another surprise was that Localstack was not remembering previous deployments. When I switch Localstack Cockpit off and start it again, I need to again do cdklocal bootstrap and redeploy everything with cdklocal deploy. I understood that by having PERSIST_ALL=true environment variable all of my settings would be saved locally but that obviously wasn’t the case.

For $15/month for each user, I think Localstack is expensive for what it offers. If the services and Console would work as expected, I would be happy to pay $50 per month just to have a local development possibility. Maybe Localstack just needs more time to support all the features they claim they support. I would rather see Localstack providing full support for a handful of AWS services instead of half-baked support for many of the AWS services.

Obvious downside of simulating AWS cloud behavior locally would be the impact of IAM policies. Something that works locally can fail in the cloud due to different level of access policies. Requests can be blocked on many levels (SCPs, permission boundaries, resource policies, user policies) but this part would really be hard to simulate locally as IAM is one of the most complex AWS services. I think I could live with this constraint knowing that what works locally might not work in the cloud because of IAM effects.

In general, this is a kind of tool every AWS developer would wish for. AWS is promoting “cloud development should not be done without cloud” mantra but that is not what developers want. Developers wants “shift-left practice” so that they can find and prevent defects early in the software delivery process. Developers want cloud to get closer to them, not the other way around. AWS should listen to developers a bit more.

I would love for AWS to buy Localstack or at least support it and make it fully compatible with many if not all of AWS service API’s. That would be a game changer!

Till then, the answer to “Is it possible to do cloud development without the cloud?” remains No. As it stands currently, an AWS sandbox account for each developer to test their code is the only way to do cloud development properly.

Leave a comment