Deploy a Python API to Amazon EKS
Containerize a Flask API, push to ECR, and deploy it on Kubernetes.
Introduction
30 Second Summary
Every app you use on your phone or laptop talks to a server somewhere. Getting your code from "it works on my machine" to running reliably in the cloud is the skill that separates side projects from production systems.
In this project, you will build a Python API, package it in a Docker container, and deploy it to a live Kubernetes cluster on AWS using Amazon EKS.
What You'll Build
A Python Flask REST API (a simple task tracker) containerized with Docker, pushed to Amazon ECR, and deployed to a live EKS Kubernetes cluster accessible via a public LoadBalancer URL.
By the end of this project, you'll have:
- A Flask API with endpoints to list tasks, create tasks, and check application health.
- A Docker container image stored in Amazon ECR, ready to deploy anywhere.
- A live EKS Kubernetes cluster with managed worker nodes running your API behind a public LoadBalancer.
- Secret Mission: Perform a rolling update to deploy a new version of your API and watch Kubernetes handle zero-downtime deployment.
Are there any prerequisites?
You will need an AWS account with the AWS CLI already configured, Python installed on your Mac, and VS Code as your code editor. If you do not have these yet, set them up before starting Step 1.
Install Docker Desktop, eksctl, and kubectl
To deploy your Flask API to Kubernetes, you need three tools on your machine.
Docker Desktop packages your app into a container. eksctl creates and manages your EKS cluster. kubectl talks to the cluster to deploy and monitor your workloads.
In this step, get ready to:
- Install Docker Desktop and verify it runs.
- Install eksctl via Homebrew.
- Install kubectl via Homebrew.
Install Docker Desktop
Docker Desktop provides the Docker engine that builds and runs containers on your Mac.
- Check if Docker is already installed by running this command in your terminal:
docker --version
✔️ I see a version number
Docker is already installed. Skip ahead to the next substep.
ⓧ Command not found
Docker is not installed yet. Follow these steps to install it.
- Open https://docs.docker.com/desktop/setup/install/mac-install/ in your browser.
- Click the download button for your Mac's chip (Apple Silicon or Intel).
- Open the downloaded .dmg file.
- Drag the Docker icon into your Applications folder.
- Press Cmd+Space to open Spotlight search.
- Type Docker and press Enter to launch Docker Desktop.
- Accept the service agreement when prompted.
- Wait for the Docker engine to start. You will see the Docker whale icon appear in your menu bar.
What is Docker Desktop?
Docker Desktop is a GUI application that runs the Docker engine on macOS. It lets you build container images and run containers locally before pushing them to a cloud registry.
- Verify Docker is working by running this command:
docker --version
You should see output containing a version number, confirming Docker is ready.
Don't see a version number?
Make sure Docker Desktop has finished starting. Look for the whale icon in your menu bar. If it is still animating, wait a moment and try again.
If you see a permissions error, open System Settings > Privacy & Security and allow Docker Desktop to run.
Help me troubleshoot my Docker Desktop installation.
Install eksctl
The eksctl CLI automates EKS cluster creation. It handles VPC setup, IAM roles, and node groups in a single command.
- Check if eksctl is already installed by running this command:
eksctl version
✔️ I see a version number
Great, eksctl is already installed. Skip ahead to the next substep.
ⓧ Command not found
Install eksctl using Homebrew with these two commands.
- Add the AWS Homebrew tap by running this command:
brew tap aws/tap
- Install eksctl by running this command:
brew install aws/tap/eksctl
- Verify the installation by running:
eksctl version
You should see a version number printed in the terminal.
Seeing a Homebrew error?
If you see a message about Homebrew not being found, you need to install Homebrew first. Visit https://brew.sh and follow the install instructions, then retry the commands above.
Help me fix my eksctl installation.
What is eksctl?
eksctl is a command-line tool built specifically for creating and managing Amazon EKS clusters. It automates the complex AWS setup (VPC, subnets, IAM roles, security groups) that would otherwise require dozens of manual steps.
Install kubectl
kubectl is the standard command-line tool for communicating with any Kubernetes cluster. You will use it to deploy your app and check on running pods.
- Check if kubectl is already installed by running this command:
kubectl version --client
✔️ I see a version number
kubectl is ready to go. You are all set for this step.
ⓧ Command not found
Install kubectl using Homebrew.
- Install kubectl by running this command:
brew install kubectl
- Verify the installation by running:
kubectl version --client
You should see client version information printed in the terminal.
Still seeing command not found?
Close your terminal and open a new one. Homebrew sometimes requires a fresh terminal session to update your PATH.
Help me troubleshoot my kubectl installation.
What is kubectl?
kubectl is the official Kubernetes command-line client. It sends instructions to the cluster's API server to create, update, and inspect resources like pods, deployments, and services.
Your toolkit is ready. Next up, you will write the Flask API that becomes your containerized application.
Write the Flask API
Your tools are installed and ready to go. Now it's time to write the actual application you'll deploy to Kubernetes.
You'll build a simple task tracker API using Flask, a lightweight Python web framework. The API will have endpoints to list tasks, create new ones, and report its own health. That health endpoint becomes critical later when Kubernetes needs to know if your app is alive.
In this step, get ready to:
- Create a project folder and install Flask.
- Write a Python API with three endpoints.
- Test the API locally to confirm it works.
Set up the project directory and install Flask
Before writing any code, you need a dedicated folder for this project and an isolated Python environment to install dependencies into.
- Switch back to your terminal.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir eks-task-api
cd eks-task-api
- Confirm the folder exists by running:
ls ~/Desktop | grep eks-task-api
You should see eks-task-api in the output.
- Create a Python virtual environment and activate it by running these commands:
python3 -m venv venv
source venv/bin/activate
What is a virtual environment?
A virtual environment is an isolated space where Python packages are installed separately from your system Python. This prevents version conflicts between different projects on your machine.
Your terminal prompt now shows (venv) at the beginning, confirming the virtual environment is active.
- Install Flask by running this command:
pip install Flask==3.1.3
What does this command do?
This installs Flask version 3.1.3 into your virtual environment. Pinning the exact version ensures your local environment matches what you'll package into a Docker container later.
You should see output ending with Successfully installed followed by Flask and its dependencies.
Seeing 'command not found' for pip?
Make sure your virtual environment is active. Your prompt should start with (venv). If it does not, run source venv/bin/activate again.
Why can't I install Flask?
- Open the project folder in VS Code by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to Desktop, select the eks-task-api folder, and click Open.
Create the Flask application
Now you'll write the API itself. The application lives in a single file called app.py.
- Create a new file called app.py by clicking the new file icon in the VS Code Explorer sidebar.
- Name it app.py and press Enter.
- Add the imports, app instance, and sample data by pasting this code into app.py:
from flask import Flask, jsonify, request
app = Flask(__name__)
# Sample task data stored in memory
tasks = [
{"id": 1, "title": "Learn Kubernetes", "done": False},
{"id": 2, "title": "Deploy to EKS", "done": False},
]
What does this code do?
- from flask import Flask, jsonify, request imports the Flask framework and two helpers for handling JSON and incoming request data.
- app = Flask(__name__) creates the Flask application instance that handles incoming HTTP requests.
- tasks is a simple in-memory list that acts as your database. In a real application, this would be a proper database.
- Add the health check and task listing endpoints below the tasks list:
@app.route("/health")
def health():
# Returns a simple status for Kubernetes health checks
return jsonify({"status": "healthy"})
@app.route("/tasks", methods=["GET"])
def get_tasks():
# Returns all tasks as JSON
return jsonify({"tasks": tasks})
What do these endpoints do?
- /health returns a simple JSON status message. Kubernetes will ping this endpoint repeatedly to confirm your app is alive.
- /tasks with a GET request returns the full list of tasks. This is the main data endpoint your users would hit.
- Add the task creation endpoint and the application runner below the get_tasks function:
@app.route("/tasks", methods=["POST"])
def create_task():
# Parse JSON from the request body
data = request.get_json()
# Build a new task with an auto-incrementing ID
new_task = {
"id": len(tasks) + 1,
"title": data.get("title", "Untitled"),
"done": False,
}
tasks.append(new_task)
return jsonify(new_task), 201
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
What does this code do?
- The POST /tasks endpoint reads JSON from the request body, creates a new task object, and appends it to the list. It returns a 201 status code meaning "created".
- if __name__ == "__main__" ensures the app only runs when executed directly (not when imported). The host="0.0.0.0" setting is needed later for Docker to expose the app outside the container.
- Save app.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete app.py file. Make sure yours matches exactly:
from flask import Flask, jsonify, request
app = Flask(__name__)
tasks = [
{"id": 1, "title": "Learn Kubernetes", "done": False},
{"id": 2, "title": "Deploy to EKS", "done": False},
]
@app.route("/health")
def health():
return jsonify({"status": "healthy"})
@app.route("/tasks", methods=["GET"])
def get_tasks():
return jsonify({"tasks": tasks})
@app.route("/tasks", methods=["POST"])
def create_task():
data = request.get_json()
new_task = {
"id": len(tasks) + 1,
"title": data.get("title", "Untitled"),
"done": False,
}
tasks.append(new_task)
return jsonify(new_task), 201
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Seeing red squiggly lines in VS Code?
VS Code might not detect your virtual environment automatically. Click the Python version in the bottom status bar, then select the interpreter path inside your venv folder.
Help me fix Python import errors in VS Code
Create requirements.txt and test the API locally
A requirements.txt file records your project's dependencies so they can be reproduced exactly inside the Docker container.
- Create a new file called requirements.txt in the VS Code Explorer sidebar (same folder as app.py).
- Paste this single line into requirements.txt:
Flask==3.1.3
Why pin the version?
Pinning Flask==3.1.3 ensures that every environment (your machine, Docker, Kubernetes) installs the exact same version. Without a pin, a future Flask release could introduce breaking changes that crash your container.
- Save requirements.txt.
- Switch back to your terminal and start the Flask development server by running:
flask run
Flask automatically discovers your app.py file and starts serving on http://127.0.0.1:5000.
- Open a new terminal tab by pressing Cmd+T (macOS) in your terminal.
- Test the health endpoint by running:
curl http://127.0.0.1:5000/health
You should see:
{"status":"healthy"}
- Test the tasks endpoint by running:
curl http://127.0.0.1:5000/tasks
You should see a JSON response containing your two sample tasks.
- Switch back to the terminal tab running Flask and press Ctrl+C to stop the server.
Getting 'Address already in use'?
Another process might be using port 5000. On macOS, AirPlay Receiver sometimes occupies this port. Open System Settings > General > AirDrop & Handoff and disable AirPlay Receiver, then try flask run again.
Help me fix the port 5000 conflict
Your API is working locally. Next up, you'll package it into a Docker container so it can run anywhere.
Containerize with Docker
Your Flask API works on your machine, but that is not enough for production. You need to guarantee it works the same way on every machine it touches.
Docker solves this by packaging your application code, dependencies, and runtime into a single portable image. In this step, you will write a Dockerfile and build a container image that runs your API anywhere Docker is installed.
In this step, get ready to:
- Create a Dockerfile for your Flask API.
- Build a Docker image from your project.
- Run the container locally and confirm the API responds.
Create a Dockerfile
A Dockerfile is a set of instructions that tells Docker how to build your image. Each instruction creates a layer, starting from a base image and adding your application on top.
- In VS Code, create a new file called Dockerfile in your eks-task-api project folder (the same folder that contains app.py).
- Paste the following code into Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
What does this Dockerfile do?
- FROM python:3.12-slim starts from a lightweight Python 3.12 image (about 120MB instead of 900MB for the full image).
- WORKDIR /app sets the working directory inside the container. All subsequent commands run from here.
- COPY requirements.txt . and RUN pip install install dependencies first. This order lets Docker cache the dependency layer so rebuilds are fast.
- COPY app.py . adds your application code after dependencies are installed.
- EXPOSE 5000 documents that the container listens on port 5000.
- CMD ["python", "app.py"] defines the command that runs when the container starts.
- Save Dockerfile.
- Confirm the file appears in your project folder alongside app.py and requirements.txt.
File not showing up?
Make sure the file is named exactly Dockerfile with a capital D and no file extension. It is not Dockerfile.txt or dockerfile.
Help me check my Dockerfile name and location
✔️ 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
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
Build and run the Docker image
Now you will turn your Dockerfile into a runnable image and launch it as a container. This proves your application works identically inside Docker as it does on your local machine.
- If your Flask development server from the previous step is still running, stop it by pressing Ctrl+C in that terminal.
- Build your Docker image by running this command:
docker build -t task-api .
What does this command do?
- docker build reads your Dockerfile and executes each instruction to create an image.
- -t task-api tags (names) the image as task-api so you can reference it later.
- . tells Docker to use the current directory as the build context (where it looks for your Dockerfile and files to copy).
- You should see Docker download the python:3.12-slim base image, install Flask, and finish with a success message.
Seeing a "Cannot connect to the Docker daemon" error?
This means Docker Desktop is not running. Open Docker Desktop from your Applications folder and wait for the whale icon in the menu bar to stop animating. Then try the build command again.
Help me troubleshoot my Docker daemon connection error
- Run your container by executing this command:
docker run -p 5000:5000 task-api
What does this command do?
docker run creates and starts a container from your task-api image.
-p 5000:5000 maps port 5000 on your machine to port 5000 inside the container. Without this flag, the container's port would be unreachable from your browser.
- Open your browser and navigate to http://localhost:5000/health.
- You should see {"status": "healthy"} in the response.
- Navigate to http://localhost:5000/tasks and confirm you see the JSON task list.
Port already in use?
If you see an error about port 5000 being in use, your Flask development server from the previous step may still be running. Press Ctrl+C in that terminal to stop it, then try the docker run command again.
Help me fix the port 5000 conflict
Your API now runs inside a container that packages everything it needs. Next up, you will push this image to Amazon ECR so your Kubernetes cluster can pull it.
Push to Amazon ECR
Your container image is built and tested locally. For your Kubernetes cluster to run this image, it needs to pull it from a container registry that AWS can access.
Amazon ECR (Elastic Container Registry) is AWS's managed Docker registry. In this step, you'll create a repository in ECR, authenticate Docker, and push your image so your EKS cluster can pull it later.
In this step, get ready to:
- Create an ECR repository in AWS.
- Push your Docker image to the repository.
Create an ECR repository and authenticate Docker
A container registry stores your Docker images so other services can download and run them. ECR integrates directly with EKS, so your cluster can pull images without extra authentication configuration.
- Create the ECR repository by running this command in your terminal:
aws ecr create-repository --repository-name task-api --region us-east-1
What does this command do?
- aws ecr create-repository tells the AWS CLI to create a new container image repository.
- --repository-name task-api names the repository task-api to match your local image name.
- --region us-east-1 places the repository in the US East (N. Virginia) region.
- You should see a JSON response in your terminal. Look for the repositoryUri field. It looks like 123456789012.dkr.ecr.us-east-1.amazonaws.com/task-api.
- Note the 12-digit number at the start of that URI. This is your AWS account ID. You will need it for the next commands.
Seeing an error about the repository already existing?
If you see RepositoryAlreadyExistsException, you already have a repository named task-api in us-east-1. That is fine. You can continue to the next command.
If you see a permissions error, confirm your AWS CLI is configured with an IAM user that has ECR access. Help me fix my AWS permissions for ECR
.
Now you need to authenticate Docker so it has permission to push images to your new ECR repository.
- 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?
aws ecr get-login-password retrieves a temporary authentication token from AWS. The pipe (|) passes that token directly into docker login so Docker can push to your private registry.
The --password-stdin flag reads the password from the pipe instead of requiring you to type it manually.
- You should see Login Succeeded in your terminal output.
Not seeing Login Succeeded?
Make sure Docker Desktop is running. The docker login command requires the Docker daemon to be active.
Double-check you replaced the account ID placeholder with your actual 12-digit AWS account ID from the previous command's output. Help me troubleshoot my ECR login
.
Tag and push the image to ECR
Docker needs to know where to push your image. You do this by tagging the local image with the full ECR repository URI.
- Tag your local image with the ECR repository URI by running this command:
docker tag task-api:latest [[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]].dkr.ecr.us-east-1.amazonaws.com/task-api:latest
- Push the tagged image to ECR by running:
docker push [[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]].dkr.ecr.us-east-1.amazonaws.com/task-api:latest
What is happening during the push?
Docker uploads your image in layers. You will see progress bars for each layer as it uploads to ECR. This may take a minute depending on your internet connection.
The :latest tag tells ECR this is the most recent version of your image. Your Kubernetes deployment will reference this tag later.
- You should see each layer pushed with a digest hash at the end confirming the upload completed successfully.
- Verify your image is in ECR by running:
aws ecr describe-images --repository-name task-api --region us-east-1
- You should see a JSON response containing an imageDetails array with your image listed, including its imageTag as latest.
Push failed or image not showing?
If the push fails with no basic auth credentials, your Docker login has expired or failed. Re-run the authentication command from the previous substep.
If describe-images returns an empty list, confirm your tag command used the exact same URI as your repository. The account ID, region, and repository name must all match. Help me debug my ECR push
.
Your container image is now stored in the cloud where any AWS service can pull it. Next up, you'll create a Kubernetes cluster and deploy your API to it.
Create an EKS Cluster and Deploy
Your container image is safely stored in ECR. Now it is time to spin up the infrastructure that will actually run your API.
In this step you will create a live EKS cluster with worker nodes, write Kubernetes manifests that tell the cluster how to run your app, and expose the API to the internet through a LoadBalancer.
In this step, get ready to:
- Create an EKS cluster with a managed node group.
- Write Kubernetes Deployment and Service manifests.
- Access your live API through a public LoadBalancer URL.
Create the EKS cluster
An EKS cluster consists of a control plane (managed by AWS) and worker nodes (EC2 instances that run your containers). You need both before you can deploy anything.
- Create your EKS cluster with 2 worker nodes by running this command:
eksctl create cluster --name task-cluster --region us-east-1 --node-type t3.small --nodes 2 --managed
What does this command do?
- --name task-cluster names your cluster so you can reference it later.
- --region us-east-1 places the cluster in the US East region.
- --node-type t3.small picks a small EC2 instance size with enough memory for your pods.
- --nodes 2 creates two worker nodes to run your containers.
- --managed tells AWS to manage the node group lifecycle (updates, replacements) for you.
This takes 10-15 minutes
Cluster creation provisions a VPC, subnets, security groups, the EKS control plane, and two EC2 instances. This is normal. Grab a coffee and wait for the terminal to return.
- Once the command finishes, verify your nodes are ready by running:
kubectl get nodes
You should see two nodes with a Ready status listed in the output.
Not seeing any nodes?
- Make sure your AWS CLI is configured for us-east-1. Run aws configure get region to check.
- If eksctl create cluster failed partway through, check the error message. A common cause is insufficient IAM permissions.
- If the command succeeded but kubectl get nodes shows nothing, eksctl may not have set your kubectl context. Run aws eks update-kubeconfig --name task-cluster --region us-east-1 and try again.
Why can't I just see my nodes?
Write and apply Kubernetes manifests
Kubernetes uses YAML manifests to declare what you want running in the cluster. You need two: a Deployment (tells Kubernetes how many copies of your container to run) and a Service (exposes those containers to the internet).
- Create a file called deployment.yaml in your project folder by running:
touch deployment.yaml
- Open deployment.yaml in VS Code and paste the following:
apiVersion: apps/v1
kind: Deployment
metadata:
name: task-api
spec:
replicas: 2
selector:
matchLabels:
app: task-api
template:
metadata:
labels:
app: task-api
spec:
containers:
- name: task-api
image: [[AWS_ACCOUNT_ID="123456789012"]].dkr.ecr.us-east-1.amazonaws.com/task-api:latest
ports:
- containerPort: 5000
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 5
periodSeconds: 5
What does this manifest do?
- replicas: 2 runs two copies of your container across the cluster for redundancy.
- image points to the ECR image you pushed in the previous step. Replace the account ID with your own.
- livenessProbe pings /health every 10 seconds. If it fails, Kubernetes restarts the container.
- readinessProbe checks /health every 5 seconds. Until it passes, Kubernetes does not send traffic to that pod.
- resources sets memory and CPU boundaries so one pod cannot starve others.
- Save deployment.yaml.
✔️ Awesome, I've got everything!
Great. Make sure you have saved the file before moving on.
ⓧ I'd like to double check the full code
apiVersion: apps/v1
kind: Deployment
metadata:
name: task-api
spec:
replicas: 2
selector:
matchLabels:
app: task-api
template:
metadata:
labels:
app: task-api
spec:
containers:
- name: task-api
image: [[AWS_ACCOUNT_ID="123456789012"]].dkr.ecr.us-east-1.amazonaws.com/task-api:latest
ports:
- containerPort: 5000
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 5
periodSeconds: 5
- Create a file called service.yaml in the same project folder by running:
touch service.yaml
- Open service.yaml in VS Code and paste the following:
apiVersion: v1
kind: Service
metadata:
name: task-api-service
spec:
type: LoadBalancer
selector:
app: task-api
ports:
- protocol: TCP
port: 80
targetPort: 5000
What does this manifest do?
- type: LoadBalancer tells AWS to provision an Elastic Load Balancer with a public URL.
- selector: app: task-api routes incoming traffic to any pod with the app: task-api label (the ones your Deployment creates).
- port: 80 accepts traffic on port 80 (standard HTTP) and forwards it to targetPort: 5000 inside the container where Flask listens.
- Save service.yaml.
✔️ Awesome, I've got everything!
Great. Make sure you have saved the file before moving on.
ⓧ I'd like to double check the full code
apiVersion: v1
kind: Service
metadata:
name: task-api-service
spec:
type: LoadBalancer
selector:
app: task-api
ports:
- protocol: TCP
port: 80
targetPort: 5000
- Apply both manifests to your cluster by running:
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
You should see output confirming both resources were created.
- Confirm your pods are running by running:
kubectl get pods
You should see 2 pods with a Running status. It may take 30-60 seconds for both to transition from ContainerCreating to Running.
Pods stuck in ImagePullBackOff?
- Double check that the image field in deployment.yaml uses your correct AWS account ID and that the image tag is :latest.
- Ensure the ECR repository is in us-east-1 (the same region as your cluster).
- The EKS node IAM role needs permission to pull from ECR. If you created the cluster with eksctl, this permission is included by default.
Help me fix my pod pulling errors
Access your live API
Your pods are running, but they are only reachable inside the cluster. The LoadBalancer Service you applied tells AWS to create an Elastic Load Balancer that gives you a public URL.
- Get the external URL for your service by running:
kubectl get svc task-api-service
What should I see?
Look for the EXTERNAL-IP column. On AWS, this is a long hostname (not a numeric IP). It looks something like a1b2c3d4-1234567890.us-east-1.elb.amazonaws.com.
If you see <pending> in the EXTERNAL-IP column, wait 1-2 minutes and run the command again. AWS is still provisioning the load balancer.
- Once you have the hostname, test your live API by running:
curl http://[[LOAD_BALANCER_URL="your-elb-hostname.us-east-1.elb.amazonaws.com"]]/tasks
You should see the JSON response from your Flask API with the list of tasks.
- Test the health endpoint as well:
curl http://[[LOAD_BALANCER_URL="your-elb-hostname.us-east-1.elb.amazonaws.com"]]/health
You should see {"status": "healthy"} returned from your live cluster.
Getting connection refused or timeout?
- Wait 2-3 minutes after the EXTERNAL-IP appears. The load balancer needs time to register healthy targets.
- Run kubectl get pods and confirm both pods show Running with 1/1 in the READY column.
- Make sure you are using http:// (not https://) in the URL. The service is configured for port 80 (HTTP).
Help me reach my LoadBalancer URL
Your API is live on the internet, running on a real Kubernetes cluster. Next up, you will take on a secret mission to perform a rolling update and watch Kubernetes replace pods with zero downtime.
Secret mission
Perform a Rolling Update
Your API is live, but what happens when you need to ship a new version? In production, you cannot take the service down while deploying. Kubernetes solves this with rolling updates, replacing pods one at a time so users never notice the switch. Time to see it in action.
Clean Up Your Resources
Clean Up Your Resources
Your EKS cluster, ECR repository, and Elastic Load Balancer are all running in AWS and accruing hourly charges. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Cost warning
Your EKS cluster costs approximately $0.16/hr combined ($0.10/hr for the control plane, ~$0.04/hr for two t3.small nodes, and ~$0.02/hr for the Load Balancer). If left running for 24 hours, that totals ~$3.84.
Delete your resources as soon as you are done experimenting. EKS clusters cannot be paused.
Resources you used:
- EKS cluster (task-cluster): control plane + 2 managed worker nodes.
- ECR repository (task-api): stores your container images.
- Elastic Load Balancer: created automatically by your Kubernetes Service.
- Local project files: app.py, Dockerfile, deployment.yaml, service.yaml, and requirements.txt.
Keep everything running
No action needed. Choose this if you are still actively experimenting with your cluster and plan to continue within the next hour.
- Your EKS cluster, ECR repository, and Load Balancer will continue incurring charges (~$0.16/hr).
- Docker Desktop remains installed on your machine (free for personal use and education).
- eksctl and kubectl remain installed via Homebrew.
Pause - I'll come back to this later
EKS clusters cannot be paused. There is no way to stop the control plane charges without fully deleting the cluster.
If you want to save money but come back later, delete the cluster now and recreate it when you are ready. Your container image in ECR can stay (negligible storage cost) so you only need to re-run the cluster creation step.
- Delete the EKS cluster to stop all compute charges by running:
eksctl delete cluster --name task-cluster --region us-east-1
What does this remove?
This command deletes the EKS control plane, managed node group, VPC, subnets, and the Elastic Load Balancer. It takes a few minutes to complete.
- Keep your ECR repository and local project files so you can redeploy faster next time.
Delete - I don't want to use this again
Remove all project resources and start fresh. This stops all charges and cleans up your AWS account.
- Delete the EKS cluster (removes the control plane, nodes, and Load Balancer) by running:
eksctl delete cluster --name task-cluster --region us-east-1
This takes a few minutes to complete. Wait for the confirmation message before continuing.
- Delete the ECR repository and all stored images by running:
aws ecr delete-repository --repository-name task-api --region us-east-1 --force
- Remove your local project files by running:
cd ~/Desktop
rm -rf task-api
- Optionally uninstall eksctl and kubectl if you no longer need them:
brew uninstall aws/tap/eksctl
brew uninstall kubectl
What about Docker Desktop?
Docker Desktop is useful for many projects beyond this one. If you want to remove it, open Finder, go to Applications, and drag Docker to the Trash.
That's a Wrap!
That's a Wrap!
Nice work. You just deployed a Python API to a live Kubernetes cluster on AWS.
You've learned how to:
- Containerize a Flask API with Docker and push the image to Amazon ECR.
- Create a live EKS cluster with managed worker nodes using eksctl.
- Deploy an application using Kubernetes Deployment and Service manifests and access it publicly via a LoadBalancer.
- Secret Mission: Performed a zero-downtime rolling update and watched Kubernetes replace pods gracefully.
Ready to quiz yourself?