Skip to main content

Deploy µQuery on AWS Lambda

Prerequisites
  • An AWS account
  • AWS CLI installed and configured (aws configure)
  • Docker installed

This tutorial walks through deploying µQuery on AWS Lambda to query files stored in S3. Authentication is handled via the AWS credential chain — the Lambda execution role is automatically detected, no access keys required.

µQuery runs as a container on Lambda using the AWS Lambda Web Adapter, which transparently proxies Lambda invocations to µQuery's HTTP server.


1. Set variables​

Define these once. All subsequent commands reference them.

export AWS_REGION="us-east-1"
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export BUCKET_NAME="uquery-data-${ACCOUNT_ID}"
export ECR_REPO="uquery"
export LAMBDA_FUNCTION="uquery"
export ROLE_NAME="uquery-lambda-role"

# Derived
export ECR_URI="${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}"
export ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/${ROLE_NAME}"

2. Create an S3 bucket​

aws s3api create-bucket \
--bucket $BUCKET_NAME \
--region $AWS_REGION \
--create-bucket-configuration LocationConstraint=$AWS_REGION
note

Skip --create-bucket-configuration if your region is us-east-1.

Upload sample data​

Download a Yellow Taxi Parquet file and upload it:

wget https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2026-01.parquet

aws s3 cp yellow_tripdata_2026-01.parquet s3://$BUCKET_NAME/

3. Create an IAM role for Lambda​

Trust policy​

cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
EOF

aws iam create-role \
--role-name $ROLE_NAME \
--assume-role-policy-document file://trust-policy.json

Attach execution policy​

Allows Lambda to write logs to CloudWatch:

aws iam attach-role-policy \
--role-name $ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Grant S3 read access​

cat > s3-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::${BUCKET_NAME}",
"arn:aws:s3:::${BUCKET_NAME}/*"
]
}]
}
EOF

aws iam put-role-policy \
--role-name $ROLE_NAME \
--policy-name uquery-s3-access \
--policy-document file://s3-policy.json

4. Build and push the Lambda image​

The AWS Lambda Web Adapter is added to the µQuery image so Lambda can proxy HTTP requests to it.

Dockerfile​

FROM fb64/uquery:latest
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 \
/lambda-adapter /opt/extensions/lambda-adapter

Build and push​

# Create ECR repository
aws ecr create-repository --repository-name $ECR_REPO --region $AWS_REGION

# Authenticate Docker to ECR
aws ecr get-login-password --region $AWS_REGION \
| docker login --username AWS --password-stdin \
${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com

# Build, tag, and push
docker build -t $ECR_URI:latest .
docker push $ECR_URI:latest

5. Deploy the Lambda function​

aws lambda create-function \
--function-name $LAMBDA_FUNCTION \
--package-type Image \
--code ImageUri=$ECR_URI:latest \
--role $ROLE_ARN \
--environment Variables="{UQ_AWS_CREDENTIAL_CHAIN=true,PORT=8080}" \
--timeout 30 \
--memory-size 512 \
--region $AWS_REGION

Create a public Function URL​

aws lambda create-function-url-config \
--function-name $LAMBDA_FUNCTION \
--auth-type NONE \
--invoke-mode RESPONSE_STREAM \
--region $AWS_REGION

export LAMBDA_URL=$(aws lambda get-function-url-config \
--function-name $LAMBDA_FUNCTION \
--region $AWS_REGION \
--query FunctionUrl \
--output text)

echo $LAMBDA_URL

6. Query your data​

Use the s3:// prefix to reference S3 files directly in SQL.

Count rows​

curl -X POST $LAMBDA_URL \
-H "Content-Type: text/plain" \
-d "SELECT COUNT(*) FROM 's3://$BUCKET_NAME/yellow_tripdata_2026-01.parquet'"

Aggregate query​

curl -X POST $LAMBDA_URL \
-H "Content-Type: text/plain" \
-H "Accept: application/json" \
-d "
SELECT
payment_type,
COUNT(*) AS trips,
ROUND(AVG(fare_amount), 2) AS avg_fare
FROM 's3://$BUCKET_NAME/yellow_tripdata_2026-01.parquet'
GROUP BY payment_type
ORDER BY trips DESC
"

Stream as Arrow IPC​

curl -X POST $LAMBDA_URL \
-H "Content-Type: text/plain" \
-H "Accept: application/vnd.apache.arrow.stream" \
-d "SELECT * FROM 's3://$BUCKET_NAME/yellow_tripdata_2026-01.parquet' LIMIT 1000" \
--output result.arrow

Check service health​

curl $LAMBDA_URL/health

By default the Function URL is public (--auth-type NONE). To require AWS IAM authentication:

aws lambda update-function-url-config \
--function-name $LAMBDA_FUNCTION \
--auth-type AWS_IAM \
--region $AWS_REGION

Callers must then sign requests with SigV4. For quick testing with your own credentials:

curl -X POST $LAMBDA_URL \
--aws-sigv4 "aws:amz:${AWS_REGION}:lambda" \
--user "$(aws configure get aws_access_key_id):$(aws configure get aws_secret_access_key)" \
-H "Content-Type: text/plain" \
-d "SELECT 1"

8. Clean up​

aws lambda delete-function --function-name $LAMBDA_FUNCTION --region $AWS_REGION
aws ecr delete-repository --repository-name $ECR_REPO --force --region $AWS_REGION
aws s3 rm s3://$BUCKET_NAME --recursive
aws s3api delete-bucket --bucket $BUCKET_NAME --region $AWS_REGION
aws iam detach-role-policy --role-name $ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role-policy --role-name $ROLE_NAME --policy-name uquery-s3-access
aws iam delete-role --role-name $ROLE_NAME
rm trust-policy.json s3-policy.json