Deploy a Scikit-Learn Model on AWS

Package an Iris classifier in Docker and deploy it as a serverless API on AWS.

Introduction

30 Second Summary

Think about the last time you asked a smart assistant for a recommendation. Behind the scenes, a trained model was running on a server, ready to answer in milliseconds.

In this project, you will take the Iris classifier you trained in Project 1 and deploy it as a live prediction API on AWS. Anyone will be able to send flower measurements to your endpoint and get back the predicted species in real time.

What You'll Build

A serverless prediction API powered by your trained scikit-learn Iris classifier, deployed on AWS Lambda and exposed through API Gateway as a public REST endpoint.

By the end of this project, you'll have:

  • A trained Random Forest model serialized and packaged inside a Docker container ready for cloud deployment.
  • A live HTTPS endpoint on AWS that accepts flower measurements and returns species predictions in real time.
  • Hands-on experience with the complete ML deployment workflow: serialize, containerize, push, deploy, and test.
  • Secret Mission: Add confidence scores and input validation to make your prediction API production-ready.

Are there any prerequisites?

This project builds on Project 1 (ML Pipeline with scikit-learn on the Iris dataset). You'll need your trained Iris classifier, an AWS account, and Docker installed on your machine.

Before We Start

Before you can deploy a model to the cloud, you need the tools that talk to the cloud. The AWS CLI is how your terminal communicates with AWS services like Lambda, ECR, and API Gateway.

In this step, you will install and configure the AWS CLI, verify Docker is running, and set up your project folder.

In this step, get ready to:
  • Install and configure the AWS CLI with your credentials.
  • Verify Docker Desktop is running.
  • Create the project folder and files.
Install and configure the AWS CLI

The AWS CLI lets you manage AWS resources directly from your terminal. You need it to push Docker images and create Lambda functions later in this project.

Windows

  • Download the AWS CLI MSI installer by opening this link in your browser:
  • https://awscli.amazonaws.com/AWSCLIV2.msi
  • Run the downloaded AWSCLIV2.msi file and follow the on-screen prompts to complete the installation.
  • Close any open terminal windows after the install finishes. You need a fresh terminal for the new aws command to be recognized.

macOS

  • Download the AWS CLI package installer by opening this link in your browser:
  • https://awscli.amazonaws.com/AWSCLIV2.pkg
  • Run the downloaded AWSCLIV2.pkg file and follow the on-screen prompts to complete the installation.
  • Close any open terminal windows after the install finishes. You need a fresh terminal for the new aws command to be recognized.

Now verify the installation worked.

  • Open a new terminal and check the AWS CLI version by running:
aws --version

✔️ I see a version number

The AWS CLI is installed and ready to configure.

ⓧ Command not found

The terminal cannot find the aws command. Try these fixes:

  • Make sure you opened a brand new terminal window after the installer finished.
  • On Windows, try opening PowerShell as Administrator and running the command again.
  • On macOS, check that /usr/local/bin is in your PATH by running echo $PATH.
  • If none of these work, re-run the installer and restart your terminal.

Next, connect the CLI to your AWS account.

  • Configure the AWS CLI with your credentials by running:
aws configure

What will aws configure ask?

The command prompts you for four values, one at a time:

  • AWS Access Key ID and AWS Secret Access Key from your IAM user in the AWS Console.
  • Default region name: enter us-east-1 (or whichever region you prefer).
  • Default output format: enter json.

Don't have an Access Key ID?

You can create one in the AWS Console under IAM. Navigate to Users, select your user, open the Security credentials tab, and click Create access key.

Copy both the Access Key ID and Secret Access Key when they appear. You will not be able to see the Secret Access Key again after closing the dialog.

help me create IAM access keys for the AWS CLI

Verify Docker is running

You already have Docker Desktop installed. You need to confirm it is actively running so you can build container images later.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Docker Desktop and press Enter to open it.
  • Wait until the Docker icon in your system tray or menu bar shows a steady state (no loading animation).
  • Verify Docker is responsive by running this in your terminal:
docker --version

✔️ I see a version number

Docker is running and ready.

ⓧ Command not found or error

Docker Desktop might not be running yet. Try these fixes:

  • Open Docker Desktop from your applications and wait for it to fully start.
  • On Windows, make sure Docker Desktop is set to use WSL 2 (check Settings > General).
  • Try closing and reopening your terminal after Docker Desktop finishes starting.
Create the project folder and files

Your project needs a dedicated folder with four files. Each file will serve a specific purpose as you build the API.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir iris-api
cd iris-api
  • Create the four project files by running:

macOS

touch train_model.py lambda_handler.py Dockerfile requirements.txt

Windows

New-Item train_model.py, lambda_handler.py, Dockerfile, requirements.txt
  • Confirm the files exist by running:
ls

You should see train_model.py, lambda_handler.py, Dockerfile, and requirements.txt in the output.

What are these files for?

  • train_model.py trains and saves the Iris classifier.
  • lambda_handler.py contains the Lambda function that loads the model and returns predictions.
  • Dockerfile packages everything into a container image for AWS Lambda.
  • requirements.txt lists the Python dependencies to install inside the container.

Don't see all four files?

Double-check that you ran the file creation command from inside the iris-api folder. Run pwd (macOS) or cd (Windows) to confirm your current directory ends in iris-api.

help me find or create the project files in the iris-api folder

Your environment is ready. Next up, you will train the Iris classifier and save it as a file that your Lambda function can load.

Train and Serialize the Model

Your project folder is set up and ready to go. Now it's time to train the machine learning model that your API will serve to the world.

Before a model can live in the cloud, it needs to be serialized into a file that another program can load later. You will train the same Random Forest classifier from Project 1, then save it as a portable .joblib file that your Lambda function can pick up and use for predictions.

In this step, get ready to:
  • Write a training script that loads the Iris dataset, trains a Random Forest model, and saves it.
  • Run the script and confirm the model is serialized to disk.
Write the training script

The training script will load the Iris dataset, split it into training and testing sets, fit a Random Forest classifier, and then serialize the trained model to a file.

  • Open train_model.py in your editor and add the following code to import libraries and prepare the data:
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import joblib

# Load the Iris dataset
iris = load_iris()
X, y = iris.data, iris.target

# Split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

What does this code do?

The first five lines import the tools you need from scikit-learn and joblib. scikit-learn provides the dataset, the classifier, and evaluation utilities. joblib handles saving the trained model to a file.

load_iris() brings in the classic Iris flower dataset with 150 samples and 4 measurements per flower. train_test_split holds back 20% of the data for testing so you can measure how well the model generalizes.

  • Below the code you just added, paste the model training and saving logic:
# Train the Random Forest classifier
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# Evaluate
accuracy = accuracy_score(y_test, model.predict(X_test))
print(f"Model accuracy: {accuracy:.2%}")

# Save the trained model
joblib.dump(model, "iris_model.joblib")
print("Model saved to iris_model.joblib")

What does this code do?

RandomForestClassifier(n_estimators=100) creates an ensemble of 100 decision trees. model.fit() trains those trees on your training data so they learn to map flower measurements to species.

accuracy_score() compares the model's predictions on the held-out test set against the true labels. Finally, joblib.dump() serializes the entire trained model into iris_model.joblib so another program can load it without retraining.

  • Save train_model.py.

Why serialize the model?

Training a model takes time and compute. Serialization saves the trained state to a file so your Lambda function can load it instantly without retraining every time a prediction request arrives. This is how production ML systems separate the training phase from the serving phase.

✔️ Awesome, I've got everything!

Great. Double check you have saved train_model.py before running it in the next substep.

ⓧ I'd like to double check the full code

from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import joblib

# Load the Iris dataset
iris = load_iris()
X, y = iris.data, iris.target

# Split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# Train the Random Forest classifier
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# Evaluate
accuracy = accuracy_score(y_test, model.predict(X_test))
print(f"Model accuracy: {accuracy:.2%}")

# Save the trained model
joblib.dump(model, "iris_model.joblib")
print("Model saved to iris_model.joblib")
Run the training script
  • In your terminal, make sure you are inside the iris-api folder, then run the training script:
python train_model.py
  • You should see two lines of output in your terminal:
Model accuracy: 100.00%
Model saved to iris_model.joblib
  • Confirm the serialized model file exists by running:
ls
  • You should see iris_model.joblib listed alongside your other project files.

Why is the accuracy 100%?

The Iris dataset is small (150 samples) and clean, with well-separated classes. A Random Forest with 100 trees easily learns these patterns perfectly on the test split. In real-world projects you would see lower accuracy, but for deployment practice this gives you a reliable model to work with.

Seeing a ModuleNotFoundError?

If you see ModuleNotFoundError: No module named 'sklearn', scikit-learn is not installed in your current Python environment. Install it by running pip install scikit-learn and then try the script again.

If you have multiple Python versions, make sure you are using the same pip that matches your python command. help me fix my ModuleNotFoundError when running train_model.py

Your trained model is now saved and ready for the cloud. Next up, you will write the Lambda handler that loads this model and serves predictions, then package everything into a Docker container.

Write the Lambda Handler and Dockerfile

You have a trained model sitting in your project folder. Now you need code that can accept flower measurements and return predictions.

In this step, you will write the Lambda handler function that serves predictions, declare the project dependency, and create a Dockerfile that packages everything into a container image ready for AWS.

In this step, get ready to:
  • Write a Lambda handler that loads the model and returns predictions.
  • Declare the scikit-learn dependency in a requirements file.
  • Create a Dockerfile that packages the handler and model into a Lambda-compatible container.
Write the Lambda handler function

When API Gateway sends a request to your Lambda function, it wraps the HTTP request in an event object. Your handler needs to parse the request body, extract the flower measurements, run the model, and return the prediction as a JSON response.

  • Open lambda_handler.py in your editor and paste in the following code:
import json
import joblib

# Load model at cold start (persists across warm invocations)
model = joblib.load("iris_model.joblib")
target_names = ["setosa", "versicolor", "virginica"]


def handler(event, context):
    try:
        body = json.loads(event.get("body", "{}"))
        features = body.get("features")

        if not features or len(features) != 4:
            return {
                "statusCode": 400,
                "body": json.dumps({
                    "error": "Please provide 4 features: sepal_length, sepal_width, petal_length, petal_width"
                })
            }

        prediction = model.predict([features])[0]
        species = target_names[int(prediction)]

        return {
            "statusCode": 200,
            "body": json.dumps({
                "prediction": int(prediction),
                "species": species
            })
        }

    except Exception as e:
        return {
            "statusCode": 500,
            "body": json.dumps({"error": str(e)})
        }

What does this code do?

The model is loaded at the top of the file, outside the handler function. This means Lambda loads it once during a cold start and reuses it across subsequent warm invocations. Only the first request pays the load cost.

The handler function parses the incoming API Gateway event body to extract a features array. It validates that exactly 4 measurements were provided, runs model.predict(), and maps the numeric prediction to a species name using the target_names list.

The function returns a dictionary with statusCode and body fields. This is the response format that API Gateway expects from a Lambda proxy integration.

Why load the model outside the handler?

Lambda keeps your function's execution environment alive between requests (a "warm" invocation). Code at the module level runs only once during the initial cold start. By loading the model there, every subsequent request reuses the already-loaded model instead of reading it from disk again.

This is a standard Lambda optimization pattern for ML inference.

  • Save lambda_handler.py.

You won't see a visible change yet. The next two files complete the container setup needed to test this code.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete lambda_handler.py for comparison:

import json
import joblib

# Load model at cold start (persists across warm invocations)
model = joblib.load("iris_model.joblib")
target_names = ["setosa", "versicolor", "virginica"]


def handler(event, context):
    try:
        body = json.loads(event.get("body", "{}"))
        features = body.get("features")

        if not features or len(features) != 4:
            return {
                "statusCode": 400,
                "body": json.dumps({
                    "error": "Please provide 4 features: sepal_length, sepal_width, petal_length, petal_width"
                })
            }

        prediction = model.predict([features])[0]
        species = target_names[int(prediction)]

        return {
            "statusCode": 200,
            "body": json.dumps({
                "prediction": int(prediction),
                "species": species
            })
        }

    except Exception as e:
        return {
            "statusCode": 500,
            "body": json.dumps({"error": str(e)})
        }
Add the project dependency

Your Lambda function needs scikit-learn to load the model and run predictions. The requirements.txt file tells Docker which Python packages to install inside the container.

  • Open requirements.txt and add this single line:
scikit-learn==1.9.0

Why only scikit-learn?

The scikit-learn package includes joblib and numpy as transitive dependencies. Pinning the version to 1.9.0 ensures the container uses the same scikit-learn version you trained with, avoiding deserialization errors.

  • Save requirements.txt.
Create the Dockerfile

A Dockerfile is a recipe that tells Docker how to build your container image. You will use the official AWS Lambda Python 3.14 base image, which includes the Lambda runtime and a local testing emulator.

  • Open Dockerfile and paste in the following:
FROM public.ecr.aws/lambda/python:3.14

COPY requirements.txt ${LAMBDA_TASK_ROOT}

RUN pip install --no-cache-dir -r requirements.txt

COPY lambda_handler.py ${LAMBDA_TASK_ROOT}
COPY iris_model.joblib ${LAMBDA_TASK_ROOT}

CMD ["lambda_handler.handler"]

What does each line do?

  • FROM public.ecr.aws/lambda/python:3.14 starts from the official AWS Lambda Python 3.14 base image hosted on Amazon ECR Public. It includes the Lambda runtime and a local testing emulator.
  • COPY requirements.txt ${LAMBDA_TASK_ROOT} copies the requirements file into the Lambda task root directory. Copying it first means Docker can cache the install layer separately from your code changes.
  • RUN pip install --no-cache-dir -r requirements.txt installs scikit-learn and its dependencies inside the container.
  • COPY lambda_handler.py ${LAMBDA_TASK_ROOT} and COPY iris_model.joblib ${LAMBDA_TASK_ROOT} add your handler code and the trained model to the image.
  • CMD ["lambda_handler.handler"] tells Lambda which function to invoke. The format is module_name.function_name.
  • Save Dockerfile.
  • Confirm all your project files are present by running:
ls

You should see Dockerfile, iris_model.joblib, lambda_handler.py, requirements.txt, and train_model.py listed in the output.

Missing a file?

Make sure you are running ls from inside the iris-api folder. If you are in a parent directory, run cd iris-api first.

If a file is missing, re-check that you created it in Step 1 and saved your edits in this step.

Help me figure out why my project files are missing.

✔️ Awesome, I've got everything!

Great. Double check you have saved all files before moving on.

ⓧ I'd like to double check the full code

Here is the complete Dockerfile for comparison:

FROM public.ecr.aws/lambda/python:3.14

COPY requirements.txt ${LAMBDA_TASK_ROOT}

RUN pip install --no-cache-dir -r requirements.txt

COPY lambda_handler.py ${LAMBDA_TASK_ROOT}
COPY iris_model.joblib ${LAMBDA_TASK_ROOT}

CMD ["lambda_handler.handler"]

Your handler, dependency file, and Dockerfile are all set. Next up, you will build the Docker image and test your prediction API locally before deploying to the cloud.

Build and Test the Container Locally

Your handler code and Dockerfile are ready. Before pushing anything to the cloud, you want to confirm the container actually works.

In this step, you will build the Docker image locally, start it up, and fire a test request at it. This catches errors early, before you wait for a cloud deployment.

In this step, get ready to:
  • Build the Docker image with the Lambda-compatible provenance flag.
  • Run the container locally using the Lambda Runtime Interface Emulator.
  • Send a test prediction request and verify the response.
Build the Docker image

Docker needs to package your handler code, the serialized model, and scikit-learn into a single image that Lambda can run.

  • Make sure your terminal is inside the iris-api folder (where the Dockerfile lives).
  • Build the image by running this command:
docker build --provenance=false -t iris-classifier .

What does this command do?

  • docker build . reads the Dockerfile in the current directory and builds an image from it.
  • -t iris-classifier tags (names) the image so you can reference it easily later.
  • --provenance=false disables Docker BuildKit provenance attestations. Lambda rejects the OCI image index that BuildKit produces by default on Windows.

This build will take 1-2 minutes as it downloads the base image and installs scikit-learn.

  • Wait for the build to complete. You should see a final line containing Successfully tagged iris-classifier:latest.

Build failing with a permissions error?

  • Make sure Docker Desktop is running. You can check by looking for the Docker whale icon in your system tray.
  • If you see a message about the Docker daemon not being reachable, restart Docker Desktop and try the build command again.

Help me troubleshoot my Docker build error.

Run the container locally

The AWS Lambda Python base image includes a built-in tool called the Runtime Interface Emulator. It simulates the Lambda execution environment on your machine, so you can invoke your handler exactly as Lambda would.

  • Start the container by running this command:
docker run -p 9000:8080 iris-classifier

What does this command do?

  • docker run creates and starts a new container from the iris-classifier image.
  • -p 9000:8080 maps port 9000 on your machine to port 8080 inside the container. The Runtime Interface Emulator listens on 8080 internally.

Your terminal is now occupied by the running container. You will open a second terminal in the next substep to send a test request.

Seeing a port conflict error?

If something else is already using port 9000, stop that process first or choose a different port like 9001:

docker run -p 9001:8080 iris-classifier

If you change the port, use the new port number in the test command in the next substep.

Help me fix a port conflict when running my Docker container.

Test the endpoint from a new terminal

With the container running, you can send it a test event that mimics what API Gateway would send to Lambda in production.

  • Open a new terminal window. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar, type Terminal or PowerShell, and press Enter.
  • Send a test prediction request by running this command:
$event = '{"body": "{\"features\": [5.1, 3.5, 1.4, 0.2]}"}'
Invoke-RestMethod -Uri "http://localhost:9000/2015-03-31/functions/function/invocations" -Method Post -Body $event -ContentType "application/json"

What does this command do?

  • The $event variable holds a JSON string that mimics the event API Gateway sends to Lambda. The body field inside it is a stringified JSON payload containing the four flower measurements.
  • Invoke-RestMethod sends a POST request to the Runtime Interface Emulator endpoint at http://localhost:9000/2015-03-31/functions/function/invocations.
  • The measurements [5.1, 3.5, 1.4, 0.2] describe a classic Iris setosa flower. Your model should predict species index 0 (setosa).
  • Verify that the response shows statusCode of 200, prediction of 0, and species of setosa.
  • Switch back to the first terminal (where the container is running) and press Ctrl+C to stop the container.

Getting a connection refused error?

  • Make sure the container is still running in the other terminal. If it crashed, check the logs for import errors.
  • Confirm you are using port 9000 in the URL. If you changed the port in the previous substep, update the URL to match.
  • If the container logs show a scikit-learn import error, the image may not have built correctly. Stop the container, rebuild with docker build --provenance=false -t iris-classifier ., and try again.

Help me troubleshoot my local Lambda container test.

Your container works locally. Next up, you will push this image to the cloud and wire it up as a live API endpoint.

Deploy to AWS and Test the Live API

In the previous step, you proved your containerized model works locally. Now it is time to put it on the internet.

In this step, you will push your Docker image to Amazon ECR, deploy it as an AWS Lambda function, and expose it through API Gateway as a public REST endpoint anyone can call.

In this step, get ready to:
  • Push your Docker image to Amazon ECR.
  • Create a Lambda function from the container image.
  • Expose the Lambda function as a public API and test it live.
Create an ECR repository and push your image

Amazon ECR is a container registry that stores your Docker images in the cloud. Lambda pulls your image from ECR when it creates your function.

  • Create the ECR repository by running this command:
aws ecr create-repository --repository-name iris-classifier

What does this command do?

This creates a private repository called iris-classifier in your AWS account. It is where your Docker image will live in the cloud.

  • You should see JSON output that includes a repositoryUri field. Copy the value before the /iris-classifier part. This is your AWS account ID and region prefix, and it looks like 123456789012.dkr.ecr.us-east-1.amazonaws.com.

Find your AWS account ID quickly

Your 12-digit account ID is the number at the start of the repositoryUri in the output above. You can also find it by running aws sts get-caller-identity and checking the Account field.

  • Authenticate Docker to your ECR registry by running this command:
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin [[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]].dkr.ecr.us-east-1.amazonaws.com

What does this command do?

This retrieves a temporary authentication token from AWS and passes it to Docker. The token lets Docker push images to your ECR registry for the next 12 hours.

  • You should see Login Succeeded in your terminal.
  • Tag your local image with the ECR repository URI by running this command:
docker tag iris-classifier:latest [[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]].dkr.ecr.us-east-1.amazonaws.com/iris-classifier:latest
  • Push the image to ECR by running this command:
docker push [[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]].dkr.ecr.us-east-1.amazonaws.com/iris-classifier:latest

This upload takes 1-2 minutes depending on your internet speed. You will see progress bars for each image layer being pushed.

Push failed or access denied?

Make sure you replaced the account ID placeholder with your actual 12-digit AWS account ID in all three commands (login, tag, and push).

If you see a token expiry error, re-run the aws ecr get-login-password command to authenticate again.

Help me troubleshoot this

Create the Lambda function in the AWS Console

Now that your image is in ECR, you can create a Lambda function that runs it. Lambda will pull the image and execute your handler whenever a request comes in.

  • Open your browser and navigate to the AWS Lambda console at https://console.aws.amazon.com/lambda.
  • Click Create function.
  • Select Container image as the function type.
  • For Function name, enter iris-classifier.
  • Click Browse images to find your ECR image.
  • Select the iris-classifier repository and choose the latest tag.
  • Click Select image.
  • Expand Change default execution role and leave it set to Create a new role with basic Lambda permissions.
  • Under Architecture, select x86_64.
  • Click Create function.

Lambda creates your function in about 30 seconds. Once you see the green success banner, you need to increase the memory and timeout.

  • Click the Configuration tab.
  • Click General configuration in the left sidebar.
  • Click Edit.
  • Set Memory to 512 MB.
  • Set Timeout to 30 seconds.
  • Click Save.

Why increase memory and timeout?

Lambda allocates CPU proportionally to memory. The default 128 MB is too low for scikit-learn to even import. At 512 MB, your model loads and predicts comfortably.

The 30-second timeout covers cold starts, which happen when Lambda loads your container image for the first time. Warm invocations (subsequent requests) complete in under a second.

Function creation failed?

If you see an error about the image not being found, make sure your ECR repository is in the same region (us-east-1) as your Lambda function.

If you see a permissions error, ensure the execution role has the AWSLambdaBasicExecutionRole policy attached. This happens automatically when you choose "Create a new role".

Help me debug my Lambda function creation

Create an API Gateway HTTP API and test the live endpoint

Your Lambda function is deployed, but it has no public URL yet. API Gateway creates an HTTPS endpoint that triggers your Lambda function whenever someone sends a request.

  • Open the API Gateway console at https://console.aws.amazon.com/apigateway.
  • Click Create API.
  • Under HTTP API, click Build.
  • Click Add integration.
  • Select Lambda from the integration type dropdown.
  • In the Lambda function field, select iris-classifier.
  • For API name, enter iris-classifier-api.
  • Click Next.

Now configure the route that maps incoming requests to your Lambda function.

  • Change the Method from ANY to POST.
  • Change the Resource path from /iris-classifier to /predict.
  • Click Next.
  • On the Define stages page, leave the default stage name as $default.
  • Click Next, then click Create.

API Gateway creates your HTTP API and shows the invoke URL on the details page. Copy this URL.

What is the invoke URL?

The invoke URL looks like https://abc123def4.execute-api.us-east-1.amazonaws.com. This is your API's public address on the internet. You will append /predict to it when sending requests.

  • Test your live API by running this command in PowerShell (replace the URL with your actual invoke URL):
$body = '{"features": [6.3, 3.3, 6.0, 2.5]}'
Invoke-RestMethod -Method Post -Uri "[[API_INVOKE_URL="your invoke URL, e.g. https://abc123.execute-api.us-east-1.amazonaws.com"]]/predict" -Body $body -ContentType "application/json"

You should see a response containing the predicted species:

{"prediction": 2, "species": "virginica"}

Why virginica?

The measurements [6.3, 3.3, 6.0, 2.5] describe a flower with long petals and wide sepals. These are characteristic of the Iris virginica species, which your Random Forest model correctly identifies.

First request is slow

The first request triggers a cold start where Lambda downloads and starts your container. This can take 10-15 seconds. Subsequent requests return in under a second because the model stays loaded in memory.

Getting a 500 error or timeout?

If you see Internal Server Error, check the Lambda function's Monitor tab and click View CloudWatch logs to see the actual error message.

If the request times out, make sure you set the timeout to 30 seconds in the Lambda Configuration tab. Cold starts with scikit-learn can take 10+ seconds.

Help me debug my API response

Your ML model is now live on the internet. Anyone with your invoke URL can send flower measurements and get back a species prediction in real time. In the Secret Mission, you will add confidence scores and input validation to make this API production-ready.

Secret mission

Add Confidence Scores and Input Validation

Your API predicts a species, but how confident is it? A production API should tell consumers how sure the model is about each prediction. It should also reject garbage inputs gracefully instead of crashing with a 500 error.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your Lambda function and API Gateway sit within the AWS Free Tier, so they cost nothing while idle.

Resources you used:

  • ECR repository (iris-classifier) storing your Docker image.
  • Lambda function (iris-classifier) running your prediction code.
  • API Gateway HTTP API exposing the /predict endpoint.
  • Local Docker image (iris-classifier) on your machine.
  • Local project folder (iris-api) with all source files.

Keep everything running

No action needed. Choose this if you plan to keep experimenting with your prediction API.

  • Your Lambda function only charges per request (1 million free requests per month).
  • Your ECR repository has 500 MB of free storage for one year.
  • Your API Gateway HTTP API has 1 million free requests per month for 12 months.
  • The local Docker image and project folder remain on your machine.

Pause - I'll come back to this later

Shut down Docker Desktop to free up memory on your machine. Your AWS resources cost nothing while idle, so no cloud changes are needed.

  • Quit Docker Desktop from its system tray icon.
  • Your Lambda function, ECR image, and API Gateway stay in place at zero cost until you make requests.
  • When you come back, start Docker Desktop again before rebuilding or pushing images.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes everything from AWS and your local machine.

Delete the ECR repository (this also removes all images inside it) by running:

aws ecr delete-repository --repository-name iris-classifier --force

Delete the Lambda function by running:

aws lambda delete-function --function-name iris-classifier
  • Delete the API Gateway HTTP API. In the AWS Console, navigate to the API Gateway service, select your API from the list, and delete it.
  • Remove the local Docker image by running:
docker rmi iris-classifier
  • Delete the local project folder. On macOS or Linux, run:
cd ~/Desktop
rm -rf iris-api

On Windows, run:

cd ~\Desktop
Remove-Item -Recurse -Force iris-api
  • Confirm the folder is gone by running ls (macOS/Linux) or dir (Windows) on your Desktop. You should no longer see iris-api in the listing.

That's a Wrap!

That's a Wrap!

Nice work! You just deployed a trained machine learning model as a live, serverless prediction API on AWS.

You've learned how to:

  • Serialized a trained scikit-learn model with joblib and containerized it using Docker with the AWS Lambda Python 3.14 base image.
  • Pushed the container image to Amazon ECR and deployed it as a serverless AWS Lambda function that serves predictions on demand.
  • Exposed the Lambda function as a public REST API using API Gateway, giving you a live HTTPS endpoint that accepts flower measurements and returns species predictions.
  • Secret Mission: Added prediction confidence scores and input validation to make the API production-ready.

Ready to quiz yourself?