DEV Community

Cover image for AI Image Recognition App with AWS (From Console to Terraform)
Fidelis Ikoroje
Fidelis Ikoroje

Posted on

AI Image Recognition App with AWS (From Console to Terraform)

I recently built an image recognition app that identifies celebrities and detects objects in photos using Amazon Rekognition. It started as a simple Lambda function triggered by S3 in a simple presentation I made before cloud enthusiasts. Now, I've evolved the setup into a full web application with a custom domain, CI/CD pipeline, and API documentation.

In this article, I'll walk you through how I did it — first the simple version using the AWS Console, then the enhanced version using Terraform.


Part 1: The Simple Version (AWS Console)

This is the foundation. You'll create a Lambda function that automatically analyzes any image you upload to S3. No frontend, no API — just upload an image to S3 and check CloudWatch for results.

What You'll Build

  • An S3 bucket to store images
  • A Lambda function that calls Amazon Rekognition
  • An S3 trigger that invokes Lambda whenever an image is uploaded

Step 1: Create an IAM Role for Lambda

Lambda needs permissions to read from S3, call Rekognition, and write logs.

  1. Go to IAM ConsoleRolesCreate role
  2. Select AWS serviceLambda → Next
  3. Attach these managed policies:
    • AmazonS3ReadOnlyAccess
    • AmazonRekognitionReadOnlyAccess
    • AWSLambdaBasicExecutionRole
  4. Name the role: rekognition-lambda-role
  5. Click Create role

IAM role creation

That's it for permissions. Lambda can now read images from S3, send them to Rekognition, and log results to CloudWatch.

Step 2: Create the S3 Bucket

This is where your images will live.

  1. Go to S3 ConsoleCreate bucket
  2. Bucket name: my-rekognition-images (must be globally unique)
  3. Region: Pick your preferred region (I used us-east-1)
  4. Leave everything else as default (Block all public access = ON)
  5. Click Create bucket

Step 3: Create the Lambda Function

This is the brain of the operation.

  1. Go to Lambda ConsoleCreate function
  2. Choose Author from scratch
  3. Settings:
    • Function name: image-recognition-processor
    • Runtime: Python 3.11
    • Architecture: x86_64
    • Execution role: Use an existing role → select rekognition-lambda-role
  4. Click Create function

Lambda function creation

Now paste this code in the function editor:

import json
import boto3
from urllib.parse import unquote_plus

rekognition_client = boto3.client('rekognition')

def lambda_handler(event, context):
    """Process S3 event and analyze image with Rekognition."""

    # Get bucket and key from the S3 event
    record = event['Records'][0]
    bucket = record['s3']['bucket']['name']
    key = unquote_plus(record['s3']['object']['key'])

    print(f"Processing image: s3://{bucket}/{key}")

    # Call Rekognition to detect labels
    response = rekognition_client.detect_labels(
        Image={
            'S3Object': {
                'Bucket': bucket,
                'Name': key
            }
        },
        MaxLabels=10,
        MinConfidence=70.0
    )

    # Log the results
    labels = response['Labels']
    print(f"\nDetected {len(labels)} labels:")
    for label in labels:
        print(f"  - {label['Name']}: {label['Confidence']:.1f}%")

    return {
        'statusCode': 200,
        'body': json.dumps({
            'image': key,
            'labels': [{'name': l['Name'], 'confidence': l['Confidence']} for l in labels]
        })
    }
Enter fullscreen mode Exit fullscreen mode

Click Deploy to save.

Step 4: Increase the Timeout

Rekognition can take a few seconds, and Lambda's default timeout is 3 seconds.

  1. Go to Configuration tab → General configurationEdit
  2. Set timeout to 30 seconds
  3. Set memory to 256 MB
  4. Save

Step 5: Add S3 Trigger

This is what makes it automatic — every image upload triggers analysis.

  1. In your Lambda function, click Add trigger
  2. Select S3
  3. Configure:
    • Bucket: my-rekognition-images
    • Event types: All object create events
    • Suffix: .jpg (optional — filters to only JPEG files)
  4. Check the acknowledgment box
  5. Click Add

 S3 trigger configuration

Step 6: Test It

  1. Go to your S3 bucket
  2. Upload any JPEG or PNG image
  3. Go to CloudWatchLog groups/aws/lambda/image-recognition-processor
  4. Open the latest log stream
  5. You'll see something like:
Processing image: s3://my-rekognition-images/test-photo.jpg

Detected 8 labels:
  - Person: 99.2%
  - Human: 99.2%
  - Outdoors: 95.1%
  - Building: 88.4%
  - City: 85.7%
  - Architecture: 82.3%
  - Urban: 78.9%
  - Metropolis: 71.2%
Enter fullscreen mode Exit fullscreen mode

CloudWatch logs with results

That's it! You now have an AI-powered image recognition pipeline. Upload an image, and within seconds Rekognition tells you what's in it.

Want Celebrity Recognition Instead?

Replace detect_labels with recognize_celebrities:

response = rekognition_client.recognize_celebrities(
    Image={
        'S3Object': {
            'Bucket': bucket,
            'Name': key
        }
    }
)

celebrities = response['CelebrityFaces']
for celeb in celebrities:
    print(f"  - {celeb['Name']}: {celeb['MatchConfidence']:.1f}%")
    if celeb.get('Urls'):
        print(f"    Links: {celeb['Urls']}")
Enter fullscreen mode Exit fullscreen mode

CloudWatch logs with results-2


Part 2: The Enhanced Version (Terraform + Web UI)

The console version is great for learning, but what if you want a proper web app? One where users can upload images from a browser, see results instantly, and access it from a custom domain?

That's what I built next. Here's the architecture:

  • Frontend: Static website on S3, served via CloudFront with a custom domain
  • Backend: API Gateway + Lambda functions (presigned URLs, processing, results)
  • Storage: S3 for images, DynamoDB for results
  • AI: Amazon Rekognition (celebrity + label detection)
  • CI/CD: GitHub Actions deploys everything automatically

Why Terraform?

Clicking through the console works for learning, but it's:

  • Slow to reproduce
  • Easy to misconfigure
  • Impossible to version control

Terraform lets you define everything as code. One command creates the entire infrastructure. Another destroys it. Push to GitHub and it deploys automatically.

Prerequisites

Before you start, you'll need:

  1. AWS Account with a registered domain in Route 53
  2. ACM Certificate (in us-east-1 for CloudFront) for your domain
  3. Terraform installed locally (download here)
  4. AWS CLI configured with your credentials
  5. GitHub account for CI/CD (optional but recommended)

Project Structure

image-recognition/
├── .github/workflows/deploy.yml    # CI/CD pipeline
├── codes/
│   ├── celebrity.py                # Standalone celebrity script
│   ├── image.py                    # Standalone label detection script
│   └── processor-web.py            # Combined processor (deployed)
├── terraform/
│   ├── lambda_packages/
│   │   ├── presign/                # Generates upload URLs
│   │   ├── processor/              # Analyzes images (auto-generated)
│   │   └── results/                # Returns results from DynamoDB
│   ├── main.tf                     # All infrastructure
│   ├── variables.tf                # Configuration variables
│   └── outputs.tf                  # Useful output values
└── website/
    ├── index.html                  # Main page
    ├── app.js                      # Frontend logic
    ├── config.js                   # API endpoint (auto-injected)
    └── styles.css                  # Styling
Enter fullscreen mode Exit fullscreen mode

Step 1: Clone the Repository

git clone https://github.com/Fidelisesq/image-recognition-app.git
cd image-recognition-app
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Variables

Edit terraform/variables.tf and update:

variable "domain_name" {
  default = "your-domain.com"  # Your domain
}
Enter fullscreen mode Exit fullscreen mode

You'll also need to set your certificate ARN and hosted zone ID (either in a terraform.tfvars file or pass them as -var flags).

Step 3: Deploy with Terraform

cd terraform
terraform init
terraform apply \
  -var="certificate_arn=arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT-ID" \
  -var="hosted_zone_id=YOUR_ZONE_ID"
Enter fullscreen mode Exit fullscreen mode

Terraform will show you everything it's about to create. Type yes to confirm.

In about 5-10 minutes, you'll have:

  • 2 S3 buckets (images + website)
  • 3 Lambda functions
  • API Gateway with endpoints
  • CloudFront distribution
  • Route 53 DNS record
  • DynamoDB table
  • All the IAM roles and policies

Step 4: Deploy the Website

After Terraform finishes, grab the outputs:

# Get the API endpoint and bucket name
terraform output api_endpoint
terraform output website_bucket
terraform output cloudfront_distribution_id
Enter fullscreen mode Exit fullscreen mode

Update website/config.js with your API endpoint, then sync:

# Upload website files
aws s3 sync ../website s3://YOUR-WEBSITE-BUCKET/ --delete

# Clear CDN cache
aws cloudfront create-invalidation --distribution-id YOUR_DIST_ID --paths "/*"
Enter fullscreen mode Exit fullscreen mode

Wait a minute or two, then visit your domain. Done!

Step 5: Automate with GitHub Actions (Optional)

If you want push-to-deploy, set up the CI/CD pipeline:

  1. Create a GitHub OIDC IAM role (guide here)
  2. Add these GitHub secrets:
    • CERTIFICATE_ARN — your ACM certificate ARN
    • HOSTED_ZONE_ID — your Route 53 zone ID
  3. Push to main — GitHub Actions handles everything

The pipeline:

  • Deploys infrastructure via Terraform
  • Auto-injects the API Gateway URL into the frontend
  • Syncs the website to S3
  • Invalidates the CloudFront cache

To destroy: push a commit with "destroy" in the message, or use workflow dispatch.

How the Web App Works

Here's the flow when a user uploads an image:

  1. Frontend calls POST /upload with filename and mode (celebrity or labels)
  2. Presign Lambda generates a presigned S3 URL and returns it
  3. Frontend uploads the image directly to S3 using the presigned URL
  4. S3 trigger fires and invokes the Processor Lambda
  5. Processor Lambda calls Rekognition, fetches Wikipedia bios (for celebrities), and saves results to DynamoDB
  6. Frontend polls GET /results/{imageId} until results are ready
  7. Results are displayed with confidence scores, bios, and links

Security Considerations

A few things I added to keep costs and abuse in check:

  • API Gateway throttling — 20 requests/second max, burst 50
  • Presigned URL expiry — Upload links valid for 5 minutes only
  • Content type validation — Server-side check for JPEG/PNG only
  • S3 private — No public access, CloudFront with OAC only
  • HTTPS enforced — TLS 1.2+ via CloudFront
  • DynamoDB TTL — Results auto-delete after 7 days
  • No secrets in code — Certificate ARN and zone ID stored as GitHub Secrets
  • Lambda Concurrency — You can set reserved concurrency to cap how many Lambda instances run simultaneously, preventing cost spikes. My account's default limit (10 concurrent executions) was already low enough to act as a natural safeguard.

Cost

For ~100 uploads per day, expect about $4-7/month. Rekognition is the main cost at $1 per 1,000 images. Everything else stays in free tier or near-zero at this scale.


Wrapping Up

The beauty of serverless is you pay nothing when nobody's using it, and it scales automatically when they are. You may see some of the use case of this app in my presentation that inspired the project here.

The complete source code is available at: github.com/Fidelisesq/image-recognition-app

Feel free to fork it, swap in your own domain, and deploy your own version. If you have questions, open an issue on the repo.

Happy building!

Top comments (0)