--- title: REST API Deployment on AWS Lambda with Terraform (2023) slug: aws-python-lambda date: 2023-10-12 taxonomies: tags: ["aws", "tutorial", "terraform", "python"] extra: medium: https://medium.com/p/87793b2a7866 --- In this article I will show the way how to deploy AWS Lambda using Terraform, with support for code updates in a single workflow. The problem with AWS Lambda is that at the time of its creation we need to have prepared code in zip archive or Docker image uploaded to ECR, unlike ECS / Kubernetes service, where we just specify the path to the repository, which can be empty at the creation stage. So to make an AWS Lambda deploy using Terraform we will need to prepare the code in the same Terraform recipe. In this article I will use the Docker image as source for AWS Lambda, as it is essentially the only one normal way to run any complex applications in Lambda that have external dependencies (libraries). There is a way to package the dependencies in a zip archive too, but if the architecture of the computer where the dependencies were built is different from the one where AWS Lambda runs, there will be problems. That’s why Docker is the most clear and reliable option. So this article will explore the creation of such resources: - Simple HTTP server on Python with FastAPI - Docker image to that application - Creating ECR storage - Build & push Docker image in Terraform file (null_resource) - Creating AWS Lambda - Creating access to Lambda from public url - Updating Lambda on code change ## Preparation As usual, we start our work by creating a new project and declaring terraform dependencies: ```sh mkdir lambda-demo; cd lambda-demo; touch main.tf ``` Describe Terraform dependencies in `main.tf` file ```tf terraform { required_providers { aws = { source = "hashicorp/aws", version = "5.17.0" } } } provider "aws" { profile = "default" region = "us-east-1" } ``` ## Creating HTTP application with FastAPI Let’s create subfolder for our code and create 3 files: ```sh mkdir code; touch code/app.py code/requirements.txt code/Dockerfile ``` Next, in the `app.py` file add the application code: ```py import os import uvicorn from fastapi import FastAPI from mangum import Mangum app = FastAPI() @app.get("/") def index(): return "Hello, from AWS Lambda!" handler = Mangum(app, lifespan="off") if __name__ == "__main__": uvicorn_app = f"{os.path.basename(__file__).removesuffix('.py')}:app" uvicorn.run(uvicorn_app, host="0.0.0.0", port=8000, reload=True) ``` This code runs the `FastAPI` service, so we can write basically as much complex API as we want. `Mangum` is a wrapper for the Lambda API, it does all the request processing logic in Lambda internally, allowing `FastAPI` to work normally. The code in the `__name__ == "__main__"` section is needed to run the application during development — we can write our API locally as usual. The following library should be added to `requirements.txt`: ``` fastapi==0.103.2 mangum==0.17.0 uvicorn==0.23.2 ``` And finally the `Dockerfile` to build an image for our application: ```Dockerfile FROM public.ecr.aws/lambda/python:3.11 ENV PYTHONUNBUFFERED=1 COPY requirements.txt ./ RUN pip3 install -r requirements.txt COPY app.py ./ CMD [ "app.handler" ] ``` ## Creating ECR We have the application ready, now we need to run it in AWS Lambda. First of all, we need to create a resource for ECR image. All the work below continues in the `main.tf` file. ```tf # --- ECR --- resource "aws_ecr_repository" "api" { name = "lambda-api" image_tag_mutability = "MUTABLE" force_delete = true image_scanning_configuration { scan_on_push = true } } ``` ## Build & Push Docker image The next step is to build the image directly in the Terraform recipe. For this purpose `null_resource` will be used. ```tf # --- Build & push image --- locals { repo_url = aws_ecr_repository.api.repository_url } resource "null_resource" "image" { triggers = { hash = md5(join("-", [for x in fileset("", "./code/{*.py,*.txt,Dockerfile}") : filemd5(x)])) } provisioner "local-exec" { command = <