FastAPI is excellent for building APIs, but getting it into production on AWS requires understanding your deployment options. This guide covers three approaches: EC2 (traditional servers), ECS with Fargate (containers), and Lambda (serverless).
Prerequisites Before we begin, ensure you have:
AWS CLI configured (aws configure)
Docker installed
A FastAPI application ready to deploy
Sample FastAPI App We’ll use this simple app throughout:
from fastapi import FastAPIfrom mangum import Mangum app = FastAPI(title="My API" , version="1.0.0" ) @app.get("/" ) def root (): return {"message" : "Hello from FastAPI on AWS!" } @app.get("/health" ) def health (): return {"status" : "healthy" } @app.get("/items/{item_id}" ) def get_item (item_id: int , q: str = None ): return {"item_id" : item_id, "query" : q} handler = Mangum(app)
# requirements.txt fastapi>=0.109.0 uvicorn>=0.27.0 mangum>=0.17.0 # For Lambda gunicorn>=21.0.0 # For EC2/ECS
Option 1: Deploy to EC2 EC2 gives you full control over the server. Best for applications needing persistent connections, background tasks, or specific system configurations.
Step 1: Launch EC2 Instance aws ec2 create-key-pair --key-name fastapi-key --query 'KeyMaterial' --output text > fastapi-key.pem chmod 400 fastapi-key.pemaws ec2 create-security-group \ --group-name fastapi-sg \ --description "FastAPI security group" aws ec2 authorize-security-group-ingress --group-name fastapi-sg --protocol tcp --port 22 --cidr 0.0.0.0/0 aws ec2 authorize-security-group-ingress --group-name fastapi-sg --protocol tcp --port 80 --cidr 0.0.0.0/0 aws ec2 authorize-security-group-ingress --group-name fastapi-sg --protocol tcp --port 443 --cidr 0.0.0.0/0 aws ec2 run-instances \ --image-id ami-0c55b159cbfafe1f0 \ --instance-type t3.micro \ --key-name fastapi-key \ --security-groups fastapi-sg \ --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=fastapi-server}]'
Step 2: Connect and Setup Server ssh -i fastapi-key.pem ec2-user@<PUBLIC_IP> sudo yum update -ysudo yum install -y python3.11 python3.11-pip nginx gitsudo mkdir -p /var/www/fastapisudo chown ec2-user:ec2-user /var/www/fastapicd /var/www/fastapipython3.11 -m venv venv source venv/bin/activatepip install fastapi uvicorn gunicorn
Step 3: Deploy Application cat > /var/www/fastapi/main.py << 'EOF' from fastapi import FastAPI app = FastAPI() @app.get("/" ) def root(): return {"message" : "Hello from FastAPI on EC2!" } @app.get("/health" ) def health(): return {"status" : "healthy" } EOF
Step 4: Setup Systemd Service sudo cat > /etc/systemd/system/fastapi.service << 'EOF' [Unit] Description=FastAPI application After=network.target [Service] User=ec2-user Group=ec2-user WorkingDirectory=/var/www/fastapi Environment="PATH=/var/www/fastapi/venv/bin" ExecStart=/var/www/fastapi/venv/bin/gunicorn main:app \ --workers 4 \ --worker-class uvicorn.workers.UvicornWorker \ --bind 127.0.0.1:8000 Restart=always [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reloadsudo systemctl enable fastapisudo systemctl start fastapi
sudo cat > /etc/nginx/conf.d/fastapi.conf << 'EOF' server { listen 80; server_name _; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $scheme ; } } EOF sudo nginx -tsudo systemctl restart nginx
Step 6: Add SSL with Certbot (Optional) sudo yum install -y certbot python3-certbot-nginxsudo certbot --nginx -d yourdomain.comsudo systemctl enable certbot-renew.timer
Option 2: Deploy to ECS with Fargate ECS Fargate is serverless containers - you don’t manage servers but get container flexibility. Best for microservices and scalable applications.
Step 1: Create Dockerfile FROM python:3.12 -slimWORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY app/ ./app/ CMD ["gunicorn" , "app.main:app" , "-w" , "4" , "-k" , "uvicorn.workers.UvicornWorker" , "-b" , "0.0.0.0:8000" ]
Step 2: Build and Push to ECR aws ecr create-repository --repository-name fastapi-app aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com docker build -t fastapi-app . docker tag fastapi-app:latest <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/fastapi-app:latest docker push <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/fastapi-app:latest
Step 3: Create ECS Cluster aws ecs create-cluster --cluster-name fastapi-cluster aws logs create-log-group --log-group-name /ecs/fastapi-app
Step 4: Create Task Definition { "family" : "fastapi-task" , "networkMode" : "awsvpc" , "requiresCompatibilities" : [ "FARGATE" ] , "cpu" : "256" , "memory" : "512" , "executionRoleArn" : "arn:aws:iam::<ACCOUNT_ID>:role/ecsTaskExecutionRole" , "containerDefinitions" : [ { "name" : "fastapi-container" , "image" : "<ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/fastapi-app:latest" , "portMappings" : [ { "containerPort" : 8000 , "protocol" : "tcp" } ] , "logConfiguration" : { "logDriver" : "awslogs" , "options" : { "awslogs-group" : "/ecs/fastapi-app" , "awslogs-region" : "us-east-1" , "awslogs-stream-prefix" : "ecs" } } , "healthCheck" : { "command" : [ "CMD-SHELL" , "curl -f http://localhost:8000/health || exit 1" ] , "interval" : 30 , "timeout" : 5 , "retries" : 3 } } ] }
aws ecs register-task-definition --cli-input-json file://task-definition.json
Step 5: Create Application Load Balancer aws elbv2 create-load-balancer \ --name fastapi-alb \ --subnets subnet-xxx subnet-yyy \ --security-groups sg-xxx aws elbv2 create-target-group \ --name fastapi-tg \ --protocol HTTP \ --port 8000 \ --vpc-id vpc-xxx \ --target-type ip \ --health-check-path /health aws elbv2 create-listener \ --load-balancer-arn <ALB_ARN> \ --protocol HTTP \ --port 80 \ --default-actions Type=forward,TargetGroupArn=<TARGET_GROUP_ARN>
Step 6: Create ECS Service aws ecs create-service \ --cluster fastapi-cluster \ --service-name fastapi-service \ --task-definition fastapi-task \ --desired-count 2 \ --launch-type FARGATE \ --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[sg-xxx],assignPublicIp=ENABLED}" \ --load-balancers "targetGroupArn=<TARGET_GROUP_ARN>,containerName=fastapi-container,containerPort=8000"
Step 7: Auto Scaling (Optional) aws application-autoscaling register-scalable-target \ --service-namespace ecs \ --scalable-dimension ecs:service:DesiredCount \ --resource-id service/fastapi-cluster/fastapi-service \ --min-capacity 1 \ --max-capacity 10 aws application-autoscaling put-scaling-policy \ --service-namespace ecs \ --scalable-dimension ecs:service:DesiredCount \ --resource-id service/fastapi-cluster/fastapi-service \ --policy-name cpu-scaling \ --policy-type TargetTrackingScaling \ --target-tracking-scaling-policy-configuration '{ "TargetValue": 70.0, "PredefinedMetricSpecification": { "PredefinedMetricType": "ECSServiceAverageCPUUtilization" }, "ScaleOutCooldown": 60, "ScaleInCooldown": 60 }'
Option 3: Deploy to Lambda with API Gateway Lambda is fully serverless - pay only for requests. Best for APIs with variable traffic, especially those with idle periods.
Step 1: Prepare Lambda Package mkdir lambda_packagecd lambda_packagepip install fastapi mangum -t . cp -r ../app .zip -r ../lambda_function.zip .
Step 2: Create Lambda Function aws iam create-role \ --role-name fastapi-lambda-role \ --assume-role-policy-document '{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole" }] }' aws iam attach-role-policy \ --role-name fastapi-lambda-role \ --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole aws lambda create-function \ --function-name fastapi-app \ --runtime python3.12 \ --handler app.main.handler \ --role arn:aws:iam::<ACCOUNT_ID>:role/fastapi-lambda-role \ --zip-file fileb://lambda_function.zip \ --timeout 30 \ --memory-size 256
Step 3: Create API Gateway aws apigatewayv2 create-api \ --name fastapi-api \ --protocol-type HTTP aws apigatewayv2 create-integration \ --api-id <API_ID> \ --integration-type AWS_PROXY \ --integration-uri arn:aws:lambda:us-east-1:<ACCOUNT_ID>:function :fastapi-app \ --payload-format-version 2.0 aws apigatewayv2 create-route \ --api-id <API_ID> \ --route-key '$default' \ --target integrations/<INTEGRATION_ID> aws apigatewayv2 create-stage \ --api-id <API_ID> \ --stage-name prod \ --auto-deploy aws lambda add-permission \ --function-name fastapi-app \ --statement-id apigateway-invoke \ --action lambda:InvokeFunction \ --principal apigateway.amazonaws.com \ --source-arn "arn:aws:execute-api:us-east-1:<ACCOUNT_ID>:<API_ID>/*"
Step 4: Using SAM for Easier Deployment AWS SAM simplifies Lambda deployments:
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Globals: Function: Timeout: 30 MemorySize: 256 Resources: FastAPIFunction: Type: AWS::Serverless::Function Properties: CodeUri: . Handler: app.main.handler Runtime: python3.12 Events: ApiEvent: Type: HttpApi Properties: Path: /{proxy+} Method: ANY RootEvent: Type: HttpApi Properties: Path: / Method: ANY Outputs: ApiUrl: Description: API Gateway URL Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com"
sam build sam deploy --guided
Comparison
Aspect
EC2
ECS Fargate
Lambda
Pricing
Per hour
Per second (vCPU/memory)
Per request + duration
Scaling
Manual/Auto Scaling
Auto (service)
Automatic
Cold starts
No
Minimal
Yes (can be significant)
Max timeout
Unlimited
Unlimited
15 minutes
WebSockets
Yes
Yes
Limited (API Gateway)
Background tasks
Yes
Yes
No (use SQS/Step Functions)
Maintenance
High
Medium
Low
Best for
Full control, persistent connections
Microservices, steady traffic
Variable traffic, cost optimization
Cost Estimation EC2 (t3.micro)
~$8.50/month (on-demand)
~$3.50/month (reserved 1-year)
ECS Fargate (0.25 vCPU, 0.5GB)
~$9/month per task running 24/7
Scales to zero = $0 when idle
Lambda
First 1M requests/month: Free
$0.20 per 1M requests after
$0.0000166667 per GB-second
Example: 1M requests @ 200ms, 256MB = ~$0.85/month
Production Checklist Security
Monitoring
CI/CD name: Deploy to ECS on: push: branches: [main ] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - name: Login to ECR uses: aws-actions/amazon-ecr-login@v2 - name: Build and push run: | docker build -t fastapi-app . docker tag fastapi-app:latest ${{ secrets.ECR_REGISTRY }}/fastapi-app:latest docker push ${{ secrets.ECR_REGISTRY }}/fastapi-app:latest - name: Update ECS service run: | aws ecs update-service --cluster fastapi-cluster --service fastapi-service --force-new-deployment
Conclusion Choose your deployment strategy based on your needs:
EC2 : Full control, persistent connections, predictable costs
ECS Fargate : Container flexibility, auto-scaling, no server management
Lambda : Lowest cost for variable traffic, zero maintenance
For most FastAPI applications, ECS Fargate offers the best balance of flexibility and ease of management. Start with Lambda if you have unpredictable traffic and can work within its constraints. Use EC2 when you need maximum control or have specific infrastructure requirements.
References