A complete guide to deploying Django applications to AWS Lambda using Docker containers, API Gateway, and automated CI/CD with GitHub Actions.
- AWS CLI installed and configured
- Docker installed
- Python 3.9+ and Django project
- GitHub account (for CI/CD)
- AWS Account with appropriate permissions
git clone https://github.com/adity1729/django-lambda-deployment.gitaws configureEnter your AWS Access Key ID, Secret Access Key, and preferred region.
Important Notes: In
lambda_handler.pyfile you can uncomment the mangum code to use mangum instead of apig_wsgi (see commit6a18276)
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export AWS_REGION="us-east-1" # Change to your preferred region
export ECR_REPO="django-serverless"
export LAMBDA_FUNCTION="django-serverless"
export IAM_ROLE="lambda-django-role"aws ecr create-repository \
--repository-name $ECR_REPO \
--region $AWS_REGION# Login to ECR
aws ecr get-login-password --region $AWS_REGION | \
docker login --username AWS --password-stdin \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
# Build Docker image for AMD64 architecture (important for Lambda compatibility)
docker buildx build --platform linux/amd64 -t $ECR_REPO:latest .
# Tag the image
docker tag $ECR_REPO:latest \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest
# Push to ECR
docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latestNote: If Docker push fails, it might have cached the wrong repository name. Use the full URI directly instead of variables.
- If you want to use exisiting role arn use this
ROLE_ARN="arn:aws:iam::123456789012:role/your-existing-lambda-role"- For creating new role arn use this
# Create trust policy file
cat > trust-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
# Create the IAM role
aws iam create-role \
--role-name $IAM_ROLE \
--assume-role-policy-document file://trust-policy.json
# Attach necessary policies
aws iam attach-role-policy \
--role-name $IAM_ROLE \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam attach-role-policy \
--role-name $IAM_ROLE \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
# Get the role ARN (save this for next step)
export ROLE_ARN=$(aws iam get-role --role-name $IAM_ROLE --query Role.Arn --output text)
echo "Role ARN: $ROLE_ARN"# Set image URI
export IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest"
# Wait for role to propagate (important!)
sleep 10
# Create Lambda function
aws lambda create-function \
--function-name $LAMBDA_FUNCTION \
--region $AWS_REGION \
--package-type Image \
--code ImageUri=$IMAGE_URI \
--role $ROLE_ARN \
--timeout 30 \
--memory-size 512 \
--environment Variables="{DJANGO_SETTINGS_MODULE=CONFIG.settings}"Important Notes:
- If you get a
ValidationExceptionerror, verify yourROLE_ARNis set correctly- Ensure your ECR image region matches your Lambda function region
- Make sure your Django
ALLOWED_HOSTSincludes the API Gateway domain or use["*"]for testing
# Create HTTP API
aws apigatewayv2 create-api \
--name django-api \
--protocol-type HTTP \
--target arn:aws:lambda:$AWS_REGION:$AWS_ACCOUNT_ID:function:$LAMBDA_FUNCTION \
--region $AWS_REGION
# Grant API Gateway permission to invoke Lambda
aws lambda add-permission \
--function-name $LAMBDA_FUNCTION \
--statement-id apigateway-invoke \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--region $AWS_REGION
# Get the API endpoint
export API_ENDPOINT=$(aws apigatewayv2 get-apis \
--region $AWS_REGION \
--query "Items[?Name=='django-api'].ApiEndpoint" \
--output text)
echo "π Deployment Complete!"
echo "API Endpoint: $API_ENDPOINT"Important Notes:
- If you see unknown name in integrations section of api gatway for lambda funtion create the api gateway with prefilled values in the command
curl $API_ENDPOINT/api/hello/Go to: Repository Settings β Secrets and variables β Actions β New repository secret
Add the following secrets:
| Secret Name | Value | Description |
|---|---|---|
AWS_ACCOUNT_ID |
Your AWS Account ID | Get from aws sts get-caller-identity |
AWS_ACCESS_KEY_ID |
Your AWS Access Key | IAM user access key |
AWS_SECRET_ACCESS_KEY |
Your AWS Secret Key | IAM user secret key |
AWS_REGION |
us-east-1 |
Your deployment region |
DJANGO_SECRET_KEY |
Your Django secret | Django SECRET_KEY setting |
Create .github/workflows/deploy.yml in your repository with the workflow configuration (see artifact above).
Update these variables in the workflow file if needed:
env:
AWS_REGION: us-east-1
ECR_REPOSITORY: django-serverless
LAMBDA_FUNCTION: django-serverlessgit add .
git commit -m "Add deployment workflow"
git push origin mainThe GitHub Action will automatically:
- Build your Docker image for AMD64
- Push to Amazon ECR
- Update Lambda function
- Display the API endpoint
When you make changes to your Django code:
# Rebuild for AMD64 architecture
docker buildx build --platform linux/amd64 -t $ECR_REPO:latest .
# Tag and push
docker tag $ECR_REPO:latest \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest
docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest
# Update Lambda function
aws lambda update-function-code \
--function-name $LAMBDA_FUNCTION \
--image-uri $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest \
--region $AWS_REGION
# Check update status
aws lambda get-function \
--function-name $LAMBDA_FUNCTION \
--region $AWS_REGION \
--query 'Configuration.LastUpdateStatus' \
--output textThis is commonly caused by architecture mismatch between your local machine and Lambda.
Solution: Always build for AMD64 architecture:
docker buildx build --platform linux/amd64 -t $ECR_REPO:latest .If you're on an M1/M2 Mac, you're building ARM64 by default, but Lambda expects AMD64.
Problem: The IAM role ARN format is incorrect or the role doesn't exist.
Solution:
# Verify role exists
aws iam get-role --role-name $IAM_ROLE
# Set ROLE_ARN explicitly
export ROLE_ARN="arn:aws:iam::$AWS_ACCOUNT_ID:role/$IAM_ROLE"Problem: ECR image and Lambda function are in different regions.
Solution: Ensure both are in the same region:
# Check ECR region
aws ecr describe-repositories --repository-name $ECR_REPO --region $AWS_REGION
# Always specify --region flag when creating LambdaProblem: API Gateway permission already exists.
Solution:
# Remove existing permission
aws lambda remove-permission \
--function-name $LAMBDA_FUNCTION \
--statement-id apigateway-invoke \
--region $AWS_REGION
# Add it again
aws lambda add-permission \
--function-name $LAMBDA_FUNCTION \
--statement-id apigateway-invoke \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--region $AWS_REGIONProblem: Django rejects requests from API Gateway.
Solution: Add API Gateway domain to ALLOWED_HOSTS in settings.py:
# For development/testing
ALLOWED_HOSTS = ["*"]
# For production (recommended)
ALLOWED_HOSTS = [
"your-api-id.execute-api.us-east-1.amazonaws.com",
"localhost",
"127.0.0.1"
]django-serverless/
βββ .github/
β βββ workflows/
β βββ deploy.yml # GitHub Actions workflow
βββ your_django_project/
β βββ settings.py # Django settings
β βββ urls.py
β βββ ...
βββ Dockerfile # Container definition
βββ requirements.txt # Python dependencies
βββ trust-policy.json # IAM role trust policy
βββ deploy.sh # Deployment script
βββ README.md # This file
- AWS Lambda Documentation
- Amazon ECR Documentation
- API Gateway Documentation
- Django Deployment Checklist
| Variable | Description | Example |
|---|---|---|
AWS_ACCOUNT_ID |
Your AWS account identifier | 123456789012 |
AWS_REGION |
AWS region for deployment | us-east-1 |
ECR_REPO |
ECR repository name | django-serverless |
LAMBDA_FUNCTION |
Lambda function name | django-serverless |
IAM_ROLE |
IAM role name for Lambda | lambda-django-role |
ROLE_ARN |
Full ARN of IAM role | arn:aws:iam::123456789012:role/... |
IMAGE_URI |
Full ECR image URI | 123456789012.dkr.ecr.us-east-1... |
API_ENDPOINT |
API Gateway endpoint URL | https://abc123.execute-api... |
- Never commit AWS credentials to your repository
- Use IAM roles with least privilege principle
- Set specific ALLOWED_HOSTS in production
- Use environment variables for sensitive configuration
- Enable CloudWatch logs for monitoring
- Implement API Gateway authentication for production APIs
- Use AWS Secrets Manager for sensitive data
# List all ECR repositories
aws ecr describe-repositories --region $AWS_REGION
# List all ECR repositories (table format)
aws ecr describe-repositories --region $AWS_REGION --output table
# Get specific repository details
aws ecr describe-repositories \
--repository-names $ECR_REPO \
--region $AWS_REGION
# List all images in a repository
aws ecr describe-images \
--repository-name $ECR_REPO \
--region $AWS_REGION
# List image tags only
aws ecr list-images \
--repository-name $ECR_REPO \
--region $AWS_REGION
# Get image details with digest
aws ecr describe-images \
--repository-name $ECR_REPO \
--image-ids imageTag=latest \
--region $AWS_REGION
# Delete an image by tag
aws ecr batch-delete-image \
--repository-name $ECR_REPO \
--image-ids imageTag=old-tag \
--region $AWS_REGION
# Delete ECR repository (including all images)
aws ecr delete-repository \
--repository-name $ECR_REPO \
--force \
--region $AWS_REGION# List all Lambda functions
aws lambda list-functions --region $AWS_REGION
# Get Lambda function details
aws lambda get-function \
--function-name $LAMBDA_FUNCTION \
--region $AWS_REGION
# Get Lambda function configuration only
aws lambda get-function-configuration \
--function-name $LAMBDA_FUNCTION \
--region $AWS_REGION
# Check Lambda function status
aws lambda get-function \
--function-name $LAMBDA_FUNCTION \
--region $AWS_REGION \
--query 'Configuration.LastUpdateStatus' \
--output text
# Invoke Lambda function directly
aws lambda invoke \
--function-name $LAMBDA_FUNCTION \
--region $AWS_REGION \
response.json
cat response.json
# Update Lambda environment variables
aws lambda update-function-configuration \
--function-name $LAMBDA_FUNCTION \
--environment Variables="{DJANGO_SETTINGS_MODULE=config.settings,DEBUG=False}" \
--region $AWS_REGION
# Update Lambda timeout
aws lambda update-function-configuration \
--function-name $LAMBDA_FUNCTION \
--timeout 60 \
--region $AWS_REGION
# Update Lambda memory
aws lambda update-function-configuration \
--function-name $LAMBDA_FUNCTION \
--memory-size 1024 \
--region $AWS_REGION
# Delete Lambda function
aws lambda delete-function \
--function-name $LAMBDA_FUNCTION \
--region $AWS_REGION# List all APIs
aws apigatewayv2 get-apis --region $AWS_REGION
# List APIs in table format
aws apigatewayv2 get-apis --region $AWS_REGION --output table
# Get specific API details
aws apigatewayv2 get-api \
--api-id <API_ID> \
--region $AWS_REGION
# Get API endpoint URL
aws apigatewayv2 get-apis \
--region $AWS_REGION \
--query "Items[?Name=='django-api'].ApiEndpoint" \
--output text
# Get API ID
aws apigatewayv2 get-apis \
--region $AWS_REGION \
--query "Items[?Name=='django-api'].ApiId" \
--output text
# Delete API Gateway
export API_ID=$(aws apigatewayv2 get-apis \
--region $AWS_REGION \
--query "Items[?Name=='django-api'].ApiId" \
--output text)
aws apigatewayv2 delete-api \
--api-id $API_ID \
--region $AWS_REGION# List all IAM roles
aws iam list-roles --query 'Roles[*].[RoleName,Arn]' --output table
# Get specific role details
aws iam get-role --role-name $IAM_ROLE
# Get role ARN
aws iam get-role \
--role-name $IAM_ROLE \
--query 'Role.Arn' \
--output text
# List policies attached to role
aws iam list-attached-role-policies --role-name $IAM_ROLE
# Detach policy from role
aws iam detach-role-policy \
--role-name $IAM_ROLE \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Delete IAM role (must detach all policies first)
aws iam delete-role --role-name $IAM_ROLE# List local Docker images
docker images
# List Docker images for specific repository
docker images | grep $ECR_REPO
# Remove Docker image
docker rmi $ECR_REPO:latest
# Remove Docker image by ID
docker rmi <IMAGE_ID>
# Remove all unused Docker images
docker image prune -a
# Check Docker buildx builders
docker buildx ls
# Create new buildx builder
docker buildx create --use
# Build for multiple platforms
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t $ECR_REPO:latest \
.
# View Docker image details
docker inspect $ECR_REPO:latest
# Check image size
docker images $ECR_REPO:latest --format "{{.Size}}"# Get AWS account ID
aws sts get-caller-identity --query Account --output text
# Get current AWS region
aws configure get region
# Get all configured settings
aws configure list
# Check who you are authenticated as
aws sts get-caller-identity
# List all available regions
aws ec2 describe-regions --output table
# Set default region
aws configure set region us-east-1# Test API endpoint
curl $API_ENDPOINT/api/hello/
# Test with verbose output
curl -v $API_ENDPOINT/api/hello/
# Test with headers
curl -H "Content-Type: application/json" $API_ENDPOINT/api/hello/
# Test POST request
curl -X POST $API_ENDPOINT/api/endpoint/ \
-H "Content-Type: application/json" \
-d '{"key":"value"}'
# Check if Docker daemon is running
docker ps
# Check Lambda function permissions
aws lambda get-policy \
--function-name $LAMBDA_FUNCTION \
--region $AWS_REGION
# Validate IAM policy
aws iam get-role-policy \
--role-name $IAM_ROLE \
--policy-name policy-name# Complete cleanup script
# WARNING: This will delete all resources!
# Delete API Gateway
export API_ID=$(aws apigatewayv2 get-apis \
--region $AWS_REGION \
--query "Items[?Name=='django-api'].ApiId" \
--output text)
aws apigatewayv2 delete-api --api-id $API_ID --region $AWS_REGION
# Delete Lambda function
aws lambda delete-function \
--function-name $LAMBDA_FUNCTION \
--region $AWS_REGION
# Delete ECR repository with all images
aws ecr delete-repository \
--repository-name $ECR_REPO \
--force \
--region $AWS_REGION
# Detach policies from IAM role
aws iam detach-role-policy \
--role-name $IAM_ROLE \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam detach-role-policy \
--role-name $IAM_ROLE \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
# Delete IAM role
aws iam delete-role --role-name $IAM_ROLE
# Delete local Docker images
docker rmi $ECR_REPO:latest
docker rmi $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest
echo "β¨ Cleanup complete!"MIT License - Feel free to use this in your projects!
Need help? Open an issue or check the troubleshooting section above.