Local Development
Introduction
Section titled “Introduction”This guide walks you through starting LocalStack and deploying a serverless API consisting of a Lambda function and a DynamoDB table. You will perform the entire deployment on your local machine without an AWS account.
A successful deployment results in a:
- LocalStack Container: Running the core emulation engine.
- Serverless API: A Lambda function with a configured Function URL.
- Persistence Layer: A DynamoDB table for message storage.
- Local Cloud Environment: A functional AWS-emulated sandbox.
Select your preferred deployment method to begin: AWS CLI or Terraform.
Prerequisites
Section titled “Prerequisites”- Docker engine installed and active.
- A LocalStack account.
Step 1: Install and Start LocalStack
Section titled “Step 1: Install and Start LocalStack”The lstk CLI provides the fastest initialization path by automating authentication and image management.
Install lstk:
brew install localstack/tap/lstkStart LocalStack:
lstk startThe first run triggers a browser-based authentication flow. After authentication, the CLI pulls the LocalStack image and initializes the container.
When the container is ready, you will see the following logs:
✔︎ LocalStack ready (containerId: 400b3e61f3c6)• Endpoint: localhost.localstack.cloud:4566• Web app: [https://app.localstack.cloud](https://app.localstack.cloud)If you are using the full-featured LocalStack CLI, ensure you have configured your auth token before starting:
localstack startWhen the container is ready, you’ll see these log lines:
__ _______ __ __ / / ____ _________ _/ / ___// /_____ ______/ /__ / / / __ \/ ___/ __ `/ /\__ \/ __/ __ `/ ___/ //_/ / /___/ /_/ / /__/ /_/ / /___/ / /_/ /_/ / /__/ ,</_____/\____/\___/\__,_/_//____/\__/\__,_/\___/_/|_|
💻 LocalStack CLI ${LOCALSTACK_VERSION}👤 Profile: default
[12:47:13] starting LocalStack in Docker mode 🐳 localstack.py:494 preparing environment bootstrap.py:1240 configuring container bootstrap.py:1248 starting container bootstrap.py:1258[12:47:15] detaching bootstrap.py:1262Step 2: Deploy serverless API
Section titled “Step 2: Deploy serverless API”Deploy the Lambda function and DynamoDB table using either the awslocal or tflocal wrappers. These tools automatically route commands to your local instance instead of AWS.
-
Install the
awslocalwrapper:Terminal window pip install awscli-local -
Create the Lambda function source. Execute the following to create a project directory and a Python handler:
Terminal window mkdir -p /tmp/localstack-democat > /tmp/localstack-demo/handler.py << 'EOF'import json, boto3, os, uuiddef handler(event, context):table = boto3.resource('dynamodb').Table(os.environ['TABLE_NAME'])method = event.get('requestContext', {}).get('http', {}).get('method', 'GET')if method == 'POST':item = {'id': str(uuid.uuid4()), **json.loads(event.get('body', '{}'))}table.put_item(Item=item)return {'statusCode': 200, 'body': json.dumps(item)}result = table.scan()return {'statusCode': 200, 'body': json.dumps(result['Items'])}EOFcd /tmp/localstack-demo && zip handler.zip handler.py -
Create the DynamoDB table:
Terminal window awslocal dynamodb create-table \--table-name Messages \--attribute-definitions AttributeName=id,AttributeType=S \--key-schema AttributeName=id,KeyType=HASH \--billing-mode PAY_PER_REQUEST -
Deploy the Lambda function:
Terminal window awslocal lambda create-function \--function-name messages-api \--runtime python3.12 \--handler handler.handler \--zip-file fileb:///tmp/localstack-demo/handler.zip \--role arn:aws:iam::000000000000:role/lambda-role \--environment Variables={TABLE_NAME=Messages}awslocal lambda wait function-active --function-name messages-api -
Configure a public URL and retrieve the endpoint:
Terminal window awslocal lambda create-function-url-config \--function-name messages-api \--auth-type NONELAMBDA_URL=$(awslocal lambda list-function-url-configs \--function-name messages-api \--query 'FunctionUrlConfigs[0].FunctionUrl' \--output text)echo $LAMBDA_URL
-
Install Terraform and the
tflocalwrapper:Terminal window pip install terraform-local -
Create a
main.tffile in a new directory:terraform {required_providers {aws = { source = "hashicorp/aws" }archive = { source = "hashicorp/archive" }}}resource "aws_dynamodb_table" "messages" {name = "Messages"billing_mode = "PAY_PER_REQUEST"hash_key = "id"attribute {name = "id"type = "S"}}data "archive_file" "lambda" {type = "zip"output_path = "${path.module}/handler.zip"source {filename = "handler.py"content = <<-EOFimport json, boto3, os, uuiddef handler(event, context):table = boto3.resource('dynamodb').Table(os.environ['TABLE_NAME'])method = event.get('requestContext', {}).get('http', {}).get('method', 'GET')if method == 'POST':item = {'id': str(uuid.uuid4()), **json.loads(event.get('body', '{}'))}table.put_item(Item=item)return {'statusCode': 200, 'body': json.dumps(item)}result = table.scan()return {'statusCode': 200, 'body': json.dumps(result['Items'])}EOF}}resource "aws_iam_role" "lambda_role" {name = "lambda-role"assume_role_policy = jsonencode({Version = "2012-10-17"Statement = [{ Action = "sts:AssumeRole", Effect = "Allow",Principal = { Service = "lambda.amazonaws.com" } }]})}resource "aws_lambda_function" "messages_api" {function_name = "messages-api"runtime = "python3.12"handler = "handler.handler"filename = data.archive_file.lambda.output_pathsource_code_hash = data.archive_file.lambda.output_base64sha256role = aws_iam_role.lambda_role.arnenvironment {variables = { TABLE_NAME = aws_dynamodb_table.messages.name }}}resource "aws_lambda_function_url" "messages_api" {function_name = aws_lambda_function.messages_api.function_nameauthorization_type = "NONE"}output "function_url" {value = aws_lambda_function_url.messages_api.function_url} -
Initialize and apply the configuration:
Terminal window tflocal init && tflocal apply -auto-approve -
Retrieve the endpoint:
Terminal window LAMBDA_URL=$(tflocal output -raw function_url)echo $LAMBDA_URL
Step 3: Test the API
Section titled “Step 3: Test the API”Send a POST request to store a message in the emulated DynamoDB table:
curl -X POST "$LAMBDA_URL" \ -H "Content-Type: application/json" \ -d '{"message": "Hello, LocalStack!"}'You will get back a response:
{ "id": "a1b2c3d4-...", "message": "Hello, LocalStack!" }Retrieve all your messages:
curl "$LAMBDA_URL"The Lambda function executes within the local environment and interacts with the emulated DynamoDB service. Because no actual cloud resources are created, you won’t incur any cloud costs or infrastructure changes.
Step 4: Inspect Resources
Section titled “Step 4: Inspect Resources”View the state of your local infrastructure via the LocalStack Web Application. Navigate to the Resource Browser to inspect your Lambda functions and DynamoDB tables in real-time.
Step 5: Clean Up
Section titled “Step 5: Clean Up”Stop your LocalStack container to remove all emulated resources. LocalStack is ephemeral by default; stopping the instance clears the state.
bash lstk stop bash localstack stop To persist state across restarts, see Persistence or Cloud Pods.
Remove the local files you created in this guide:
rm -rf /tmp/localstack-demoNext steps
Section titled “Next steps”You have successfully deployed and tested a serverless API on your local workstation. Proceed to the CI/CD guide to learn how to integrate LocalStack into your automated testing pipelines and GitHub Actions workflows.