Quickstart
Introduction
Section titled “Introduction”In this quickstart you’ll start LocalStack and deploy a simple serverless API — a Lambda function backed by DynamoDB — entirely on your local machine. No AWS account needed.
By the end you will have:
- LocalStack running locally in Docker
- A Lambda function deployed and invokable via a public URL
- A DynamoDB table storing data written by the Lambda
- Confirmed that your local environment behaves like real AWS
Choose your preferred deployment style below: AWS CLI (awslocal) or Terraform (tflocal).
Prerequisites
Section titled “Prerequisites”- Docker installed and running
- A LocalStack account
Step 1 — Install and start LocalStack
Section titled “Step 1 — Install and start LocalStack”The fastest way to get LocalStack running locally is with lstk, a lightweight CLI that handles authentication and image setup automatically.
Install lstk:
brew install localstack/tap/lstk # macOS / Linux with Homebrewnpm install -g @localstack/lstk # or via npmThen start LocalStack:
lstk startOn first run, lstk opens a browser login to authenticate, then pulls the image and starts the container automatically.
If you prefer the full-featured LocalStack CLI, install it first, then configure your auth token, and start LocalStack:
localstack startWait for the container to report ready — you’ll see a log line like Ready. or you can verify with:
curl -s http://localhost:4566/_localstack/health | grep '"running"'Step 2 — Deploy the serverless API
Section titled “Step 2 — Deploy the serverless API”Now deploy a Lambda function and a DynamoDB table. Pick the tooling you prefer:
Install the awslocal wrapper if you haven’t already:
pip install awscli-localCreate the Lambda function:
mkdir -p /tmp/localstack-democat > /tmp/localstack-demo/handler.py << 'EOF'import json, boto3, os, uuid
def 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.pyCreate the DynamoDB table:
awslocal dynamodb create-table \ --table-name Messages \ --attribute-definitions AttributeName=id,AttributeType=S \ --key-schema AttributeName=id,KeyType=HASH \ --billing-mode PAY_PER_REQUESTDeploy the Lambda:
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-apiCreate a public function URL:
awslocal lambda create-function-url-config \ --function-name messages-api \ --auth-type NONERetrieve the URL:
LAMBDA_URL=$(awslocal lambda list-function-url-configs \ --function-name messages-api \ --query 'FunctionUrlConfigs[0].FunctionUrl' \ --output text)echo $LAMBDA_URLInstall Terraform and the tflocal wrapper:
brew install hashicorp/tap/terraform # or see https://developer.hashicorp.com/terraform/installpip install terraform-localCreate a project directory and main.tf:
mkdir -p /tmp/localstack-demo && cd /tmp/localstack-demoterraform { 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 = <<-EOF import json, boto3, os, uuid def 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_path source_code_hash = data.archive_file.lambda.output_base64sha256 role = aws_iam_role.lambda_role.arn environment { variables = { TABLE_NAME = aws_dynamodb_table.messages.name } }}
resource "aws_lambda_function_url" "messages_api" { function_name = aws_lambda_function.messages_api.function_name authorization_type = "NONE"}
output "function_url" { value = aws_lambda_function_url.messages_api.function_url}Deploy:
tflocal inittflocal apply -auto-approveRetrieve the URL:
LAMBDA_URL=$(tflocal output -raw function_url)echo $LAMBDA_URLStep 3 — Test the API
Section titled “Step 3 — Test the API”Store a message:
curl -X POST "$LAMBDA_URL" \ -H "Content-Type: application/json" \ -d '{"message": "Hello, LocalStack!"}'You should get back a response like:
{ "id": "a1b2c3d4-...", "message": "Hello, LocalStack!" }List all messages:
curl "$LAMBDA_URL"That’s the win. You just invoked a real Lambda function that wrote to a real DynamoDB table — all running locally, with no AWS account and no cloud costs.
Step 4 — Inspect your resources
Section titled “Step 4 — Inspect your resources”You can browse the resources you just deployed in the LocalStack Web Application. Navigate to your Default Instance and click through to Lambda or DynamoDB to see your running infrastructure.
Step 5 — Clean up
Section titled “Step 5 — Clean up”When you’re done, stop LocalStack to tear down all local resources:
lstk stop # if using lstklocalstack stop # if using the LocalStack CLILocalStack is ephemeral by default — stopping it removes all provisioned resources. To persist state across restarts, see Persistence or Cloud Pods.
Next steps
Section titled “Next steps”- Tutorials — Deeper dives into specific AWS services and application stacks
- Supported Services — Full list of emulated AWS services
- CI/CD Setup — Run LocalStack in GitHub Actions and other pipelines
- AI & Agent Workflows — Use LocalStack with AI coding tools and agents
- Tooling —
awslocal,tflocal, LocalStack Desktop, and more