A video for this project is coming soon (like this). The source code is split into three repos: (api), (frontend), and (manifests).
What I Built
I built a voting app where people pick their favorite of three movies: Star Wars, Avatar, or Avengers. I hosted the whole thing on AWS with Elastic Kubernetes Service (EKS).
Frontend: A React interface where people submit their votes.
Backend: A Go API that takes votes and updates the database.
Database: MongoDB, which stores the vote records.
Architecture

VPC (Virtual Private Cloud)
The private network that holds everything. Mine runs inside AWS.
Cluster
A Kubernetes cluster is a group of nodes (machines) that run containers together. It’s the core of the whole setup.
Namespace
A namespace splits up cluster resources. Think of it as a smaller cluster inside the real one.
Services
A Service defines a set of Pods and how to reach them. I used a ClusterIP service for the MongoDB StatefulSet and LoadBalancer services for the API and frontend.
- mongo: A ClusterIP service. It’s only reachable from inside the cluster. The API uses it to reach the MongoDB StatefulSet.
- api: A LoadBalancer service. It’s reachable from outside the cluster. It points to the API Deployment.
- frontend: A LoadBalancer service. It’s reachable from outside the cluster. It points to the frontend Deployment.
StatefulSet
A StatefulSet is built for stateful apps like databases. I used it for MongoDB because it needs persistent storage and stable network names. Mine runs three replica pods for availability. The primary pod handles reads and writes. The secondaries keep copies of the data.
- pod1, pod2, pod3: Each one is a MongoDB instance inside the StatefulSet.
- pvc0, pvc1, pvc2 and pv0, pv1, pv2: These are Persistent Volume Claims (PVC) and Persistent Volumes (PV).
- PVC (Persistent Volume Claim): A request for storage. A PVC uses up PV resources, the same way a Pod uses up node resources.
- PV (Persistent Volume): A chunk of storage added to the cluster through a StorageClass. Mine uses gp2.
Deployments
Deployments run stateless apps. They create Pods and scale them. I have two Deployments, each with a ReplicaSet. One is for the API and one is for the frontend.
api: Runs the Go API that handles votes and talks to the database.
frontend: Runs the React interface people use to vote.
- ReplicaSet: Keeps the right number of Pod replicas running at all times. This gives you self-healing and scaling.
How a Vote Flows
- A user submits a vote on the frontend.
- The frontend sends it to the API.
- The API updates the database.
- The database saves it and responds to the API.
- The API tells the frontend the vote went through.
- The user sees the new count.
What You Need
You need an AWS account with the AWS CLI installed. You also need kubectl and Docker.
Install links:
- AWS CLI
- kubectl
- docker and, depending on your machine, docker buildx
Initial Setup
IAM User
I followed least privilege and created a new IAM user just for this. Mine is called ‘kubectl’ and has my kubectl-policy custom policy attached. I used this user to configure AWS in my terminal.

The kubectl-policy policy lets the user describe, list, and access the EKS cluster.

Here’s the policy in JSON:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"eks:DescribeCluster",
"eks:ListClusters",
"eks:DescribeNodegroup",
"eks:ListNodegroups",
"eks:ListUpdates",
"eks:AccessKubernetesApi"
],
"Resource": "*"
}]
}
IAM Cluster Role
For the cluster role, I created a new role and attached the Amazon EKS Cluster IAM Role policy.

IAM Node Group Role
For the node group role, I created a new role and attached four AWS managed policies.

Setting Up the EKS Cluster
Creating the Cluster
I created a new EKS cluster with the default settings and attached my cluster role.
Once it was up, I made a node group with two t2.medium nodes and attached my node group role.
I also added the Amazon EBS CSI Driver under add-ons. It creates EBS volumes on demand when a PVC asks for storage. MongoDB’s StatefulSet won’t work without it.

Connecting kubectl and Fixing the Config Map
With the cluster and node group ready, I had to connect kubectl to the cluster. kubectl is the command line tool for working with Kubernetes.
First I configured the AWS CLI with my IAM user’s access keys.
aws configure
I checked that it worked with:
aws sts get-caller-identity
It returned the ARN of my IAM user.
Then I set the kubectl context:
aws eks update-kubeconfig --name <eks-cluster-name> --region <aws-region>
Next I wanted to create a namespace and deploy MongoDB. But my IAM user didn’t have permission. Running kubectl get nodes gave me error: You must be logged in to the server (Unauthorized). The user had no access to the cluster yet.
If I ran aws configure with the root user’s keys instead, kubectl get nodes worked fine. That’s because the root user created the cluster.
To fix it, I edited the aws-auth config map. The easiest way was AWS Cloud Shell, which already has root credentials. I ran kubectl edit configmap aws-auth -n kube-system
It returned:
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::007575380756:role/<IAM-role>
username: system:node:{{EC2PrivateDNSName}}
kind: ConfigMap
metadata:
creationTimestamp: "2024-01-25T09:43:30Z"
name: aws-auth
namespace: kube-system
resourceVersion: "1366"
uid: 1e2ae7a7-db28-4fb7-aa8e-9ce9b8cc98b0
I added a mapUsers section under mapRoles like this:
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::007575380756:role/<IAM-role>
username: system:node:{{EC2PrivateDNSName}}
mapUsers: |
- userarn: arn:aws:iam::007575380756:user/<IAM-user>
username: <IAM-user>
groups:
- system:masters
kind: ConfigMap
metadata:
creationTimestamp: "2024-01-25T09:43:30Z"
name: aws-auth
namespace: kube-system
Back in my terminal with the IAM user’s credentials, kubectl get nodes worked. The nodes showed as ready:
NAME STATUS ROLES AGE VERSION
ip-172-31-33-204.ec2.internal Ready <none> 53m v1.29.0-eks-5e0fdde
ip-172-31-7-200.ec2.internal Ready <none> 53m v1.29.0-eks-5e0fdde
Deploying the Resources
I deployed everything with manifest files. A manifest is a YAML file that describes a Kubernetes resource. You apply one with kubectl apply -f <file-name>.
Here’s what each manifest does:
- mongo-statefulset.yaml: Defines the MongoDB StatefulSet. It sets the image, replica count, storage claims, and the replica set config.
- mongo-service.yaml: Creates a ClusterIP Service for MongoDB. It works like an internal load balancer so other pods can reach the MongoDB pods. Its selectors match the labels on the StatefulSet.
- mongo-secret.yaml: Creates a Secret for the MongoDB username and password. The API reads it so the credentials aren’t hardcoded.
- api-deployment.yaml: Defines the Deployment for the Go API. It sets the image, replica count, and the environment variables the API needs to reach MongoDB. Those point to the MongoDB secret.
- api-service.yaml: Creates a LoadBalancer Service for the API, since it needs outside access. It maps an external port to the container port.
- frontend-deployment.yaml: Defines the Deployment for the React frontend. It sets the image, replica count, and environment variables, including the API host so the frontend can reach the API.
- frontend-service.yaml: Creates a LoadBalancer Service for the frontend so users can reach it from outside the cluster.
Creating a Namespace
I ran kubectl create ns <namespace-name> to create the namespace.
Then kubectl get ns to confirm it was there.
Deploying the MongoDB StatefulSet
Next I ran kubectl apply -f mongo-statefulset.yaml to apply the StatefulSet to the cluster.
Here’s mongo-statefulset.yaml with comments:
apiVersion: apps/v1
kind: StatefulSet # Defines the type of Kubernetes object (StatefulSet)
metadata:
name: mongo
namespace: <namespace-name>
spec: # specification defining the desired state of the StatefulSet.
serviceName: mongo # name used to connect to the pods
replicas: 3 # number of pods
selector: # identifies the set of pods
matchLabels:
role: db # this label selector determines which pods are part of the group
template: # describes the pods that will be created.
metadata:
labels:
role: db # Labels assigned to the pods
env: demo # env label
replicaset: rs0.main # the replica set label (mongo replicaset)
spec: # pod specification
affinity: # Affinity settings for the pod placement.
podAntiAffinity: # Defines rules to separate pods from each other.
preferredDuringSchedulingIgnoredDuringExecution: # Soft rule for anti-affinity.
- weight: 100 # Weight associated with the scheduling preference.
podAffinityTerm:
labelSelector:
matchExpressions:
- key: replicaset
operator: In
values:
- rs0.main # Ensures pods with the same replicaset label (rs0.main) are not on the same host.
topologyKey: kubernetes.io/hostname # Focuses on the hostname of the node for pod placement.
terminationGracePeriodSeconds: 10
containers:
- name: mongo # Name of the container.
image: mongo:4.2 # The Docker image to use for the container.
command: # Command to run inside the container.
- "numactl" #Command to control NUMA policy
- "--interleave=all" #Option to interleave memory allocations across all NUMA nodes
- "mongod" #Command to run the MongoDB daemon
- "--wiredTigerCacheSizeGB" #Option to specify WiredTiger cache size (GB)
- "0.1" #Cache size
- "--bind_ip" # Option to specify the IP address to bind to
- "0.0.0.0" # MongoDB will listen for incoming connections on all network interfaces
- "--replSet" # Used to specify the name of the replica set in MongoDB.
- "rs0" # Name of the MongoDB replica set
ports:
- containerPort: 27017 # The port on which the container is listening.
volumeMounts: #attaches the persistent volume to the container
- name: mongodb-persistent-storage-claim
mountPath: /data/db
volumeClaimTemplates: # Templates for creating persistent volume claims(PVC).
- metadata:
name: mongodb-persistent-storage-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: gp2 #type of storage that is provided by AWS EBS
resources:
requests:
storage: 0.5Gi # Amount
To watch the pods come up, I ran kubectl get pods -n <namespace-name> -w. The -w flag watches and -n sets the namespace. The output looked like this:
AME READY STATUS RESTARTS AGE
mongo-0 0/1 ContainerCreating 0 6s
mongo-0 1/1 Running 0 7s
mongo-1 0/1 Pending 0 0s
mongo-1 0/1 Pending 0 0s
mongo-1 0/1 ContainerCreating 0 0s
mongo-1 1/1 Running 0 25s
mongo-2 0/1 Pending 0 0s
mongo-2 0/1 Pending 0 0s
mongo-2 0/1 ContainerCreating 0 0s
mongo-2 1/1 Running 0 14s
I also checked the persistent volume claims with kubectl get pvc -n <namespace-name>
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
mongodb-persistent-storage-claim-mongo-0 Bound pvc-d93f7cc3-c922-4f0d-825f-3ab261e10594 1Gi RWO gp2 <unset> 25m
mongodb-persistent-storage-claim-mongo-1 Bound pvc-6f411063-8946-4e57-aa91-456733a33100 1Gi RWO gp2 <unset> 4m33s
mongodb-persistent-storage-claim-mongo-2 Bound pvc-ca7aa5aa-0f8b-41b2-9a92-931ab1f7e567 1Gi RWO gp2 <unset> 4m8s
Creating a Service for the StatefulSet
I exposed MongoDB with a headless service (no ClusterIP). It’s internal only and not reachable from the internet. It lets other pods connect to each StatefulSet pod directly.
Here’s mongo-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: mongo
namespace: <namespace-name>
labels:
role: db
env: demo
spec:
ports:
- port: 27017 #where the service will be listening
targetPort: 27017 #where the service will be routing traffic to
clusterIP: None #what makes the service headless
selector: #identifies which pods this service will route traffic to
role: db
I created it with kubectl apply -f mongo-service.yaml and got back
service/mongo created
Then I checked it:
kubectl get svc -n <namespace-name>
The output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mongo ClusterIP None <none> 27017/TCP 2m10s
Setting Up Replication and Adding Data
With the service in place, I set up replication. I opened the mongo shell inside the mongo-0 pod.
kubectl exec -it mongo-0 -n <namespace-name> -- mongo
I made mongo-0 the primary. mongo-1 and mongo-2 became the secondaries.
I ran these commands in the mongo shell:
# initialize the MongoDB replica set
rs.initiate();
# set a delay of 2 seconds to allow rs.initiate() to complete
sleep(2000);
# add a secondary node to the replica set along with its hostname and port
rs.add("mongo-1.mongo:27017");
# add another secondary node
rs.add("mongo-2.mongo:27017");
# set a delay to allow secondary nodes to join the replica set and sync data
sleep(2000);
# fetch the current replica set configuration and store it in the 'cfg' variable
cfg = rs.conf();
# change configuration to specify the hostname of the primary node
cfg.members[0].host = "mongo-0.mongo:27017";
# apply the new configuration to the replica set
rs.reconfig(cfg, {force: true});
# set a 5 second delay for the replica set to stabilize and complete the reconfiguration process
sleep(5000);
Then I switched to the moviedb database with use moviedb; and inserted the movie data. The database gets created automatically on the first insert.
db.movies.insert({
"title": "avengers",
"moviedetail": {
"director": "Joss Whedon",
"genre": "Action, Adventure, Sci-Fi",
"releaseYear": 2012,
"votes": 0
}
});
db.movies.insert({
"title": "avatar",
"moviedetail": {
"director": "James Cameron",
"genre": "Action, Adventure, Fantasy",
"releaseYear": 2009,
"votes": 0
}
});
db.movies.insert({
"title": "starwars",
"moviedetail": {
"director": "George Lucas",
"genre": "Action, Adventure, Fantasy",
"releaseYear": 1977,
"votes": 0
}
});
I checked the data with db.movies.find().pretty(); and got:
rs0:PRIMARY> db.movies.find().pretty();
{
"_id" : ObjectId("65b393287f665c0281d522e5"),
"title" : "avengers",
"moviedetail" : {
"director" : "Joss Whedon",
"genre" : "Action, Adventure, Sci-Fi",
"releaseYear" : 2012,
"votes" : 0
}
}
{
"_id" : ObjectId("65b393377f665c0281d522e6"),
"title" : "avatar",
"moviedetail" : {
"director" : "James Cameron",
"genre" : "Action, Adventure, Fantasy",
"releaseYear" : 2009,
"votes" : 0
}
}
{
"_id" : ObjectId("65b3933f7f665c0281d522e7"),
"title" : "starwars",
"moviedetail" : {
"director" : "George Lucas",
"genre" : "Action, Adventure, Fantasy",
"releaseYear" : 1977,
"votes" : 0
}
}
Creating the MongoDB Secret
Next I created a secret to hold the MongoDB username and password.
Here’s mongo-secret.yaml:
apiVersion: v1
kind: Secret
metadata:
name: mongodb-secret
namespace: <namespace-name>
data:
username: YWRtaW4= # This is 'admin' in Base64 encoding
password: cGFzc3dvcmQ= # This is 'password' in Base64 encoding
# in kubernetes secrets are stored in base64 encoding
I ran kubectl apply -f mongo-secret.yaml and got back secret/mongodb-secret created
Deploying the API
The API updates the database every time someone votes. I wrote it in Go.
I deployed it with kubectl apply -f api-deployment.yaml and got back
deployment.apps/api created
Here’s api-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: <namespace-name>
labels:
app: api
env: demo
spec:
replicas: 2 #two pods (containers) created
strategy:
type: RollingUpdate #rolling update for zero downtime
rollingUpdate:
maxSurge: 1 #pods that can be created above 2
maxUnavailable: 25% # % of pods that can be unavailable during update
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: davidsvida/api-deploy:v7 #docker image
imagePullPolicy: Always # ensure latest image
env: #env variables for the container
- name: MONGO_CONN_STR
value: mongodb://mongo-0.mongo,mongo-1.mongo,mongo-2.mongo:27017/moviedb?replicaSet=rs0
- name: MONGO_USERNAME
valueFrom:
secretKeyRef: #to retrieve value from k8s secret
name: mongodb-secret
key: username
- name: MONGO_PASSWORD
valueFrom:
secretKeyRef:
name: mongodb-secret
key: password
ports:
- containerPort: 8080 #listening port
livenessProbe: #check container health
httpGet:
path: /ok
port: 8080
initialDelaySeconds: 2 #wait 2 seconds before checking
periodSeconds: 5 #check every 5 seconds
readinessProbe: # check if container is ready to receive traffic
httpGet:
path: /ok
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1 #after 1 successful check, container is ready
I checked with kubectl get pods -n <namespace-name> and saw:
NAME READY STATUS RESTARTS AGE
api-579856b9cd-f255n 1/1 Running 0 88s
api-579856b9cd-kgch8 1/1 Running 0 88s
mongo-0 1/1 Running 0 4h54m
mongo-1 1/1 Running 0 4h53m
mongo-2 1/1 Running 0 4h53m
Creating a Service for the API
This step needs an SSL certificate from AWS Certificate Manager (ACM). ACM certificates for your own domain are free. I grabbed the certificate ARN and swapped it in for arn:aws:acm:region:account-id:certificate/certificate-id in api-service.yaml.

Here’s api-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: api
namespace: <namespace-name>
annotations:
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:region:account-id:certificate/certificate-id" #for our ELB to use SSL
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http" #for internal communication
labels:
app: api
spec:
selector:
app: api
ports:
- protocol: TCP
port: 443 #indicates listening HTTPS traffic
targetPort: 8080
type: LoadBalancer #type of service(LoadBalancer)
I exposed the API through a load balancer with kubectl apply -f api-service.yaml and got back service/api created
To check it, I ran kubectl get svc -n <namespace-name> and saw both services:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api LoadBalancer 10.100.120.76 af6d6ac51fafc48cc92ac450ac73c1b0-936014577.us-east-1.elb.amazonaws.com 443:31938/TCP 4s
mongo ClusterIP None <none> 27017/TCP 132m
Pointing DNS at the Load Balancer
The api-service.yaml annotation service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:region:account-id:certificate/certificate-id" tells the ELB to use my ACM certificate. For that certificate to actually work, the load balancer needs a subdomain on the domain the certificate covers. So I created a CNAME record in Route 53 pointing to the EXTERNAL-IP of the API service.
It took a few minutes for the load balancer and DNS to go live. Then I added /movies to the end of the DNS name and got back the data I inserted earlier.

Building the Frontend Image
The React frontend reads the API address from the .env file. Since React compiles into a static site, I had to set this before building the Docker image.
Clone the Repo
- I cloned the frontend repo:
git clone https://github.com/davidsvida/frontend-deploy
Update the .env File
- I opened the
.envfile:
vim .env
- Then I set
REACT_APP_APIHOSTPORTto the API subdomain I created in Route 53:
REACT_APP_APIHOSTPORT=<subdomain>.<domain>.<tld>
Build and Push the Image
Next I built the image and pushed it to Docker Hub. I was on a Mac, so I used buildx to build for both arm64 (Apple Silicon) and amd64 (Intel).
Check that buildx is installed with docker buildx version first. If it isn’t, run docker buildx install
docker buildx create --name mybuilder --use
docker buildx inspect --bootstrap
docker buildx build --platform linux/amd64,linux/arm64 -t <your-dockerhub-username>/<your-dockerhub-repo-name>:<version-number> . --push
What each line does:
docker buildx create --name mybuilder --use: Creates a builder calledmybuilderand makes it the active one.docker buildx inspect --bootstrap: Inspects the builder and gets it ready to use.docker buildx build --platform linux/amd64,linux/arm64 -t <your-dockerhub-username>/<your-dockerhub-repo-name>:<version-number> . --push: Builds the image for arm64 and amd64 and pushes it to Docker Hub.
A Note on This Step
Updating the .env file by hand isn’t ideal. A serverless API or a CI/CD pipeline could automate it. I didn’t have time to build either for this project.
Deploying the Frontend
With the image on Docker Hub, I deployed the frontend and pointed it at that image.
Here’s frontend-deployment.yaml. It’s close to api-deployment.yaml. The main differences are the image, environment variables, and label selectors.
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: <namespace-name>
labels:
app: frontend
env: demo
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 25%
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: <your-dockerhub-username>/<your-dockerhub-repo-name>:<version-number> #the docker image we built and pushed to docker hub
imagePullPolicy: Always
env:
- name: API_HOST_PORT
value: "<subdomain>.<domain>.<tld>" #the environment variable previously set in the .env file
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 3
I ran kubectl apply -f frontend-deployment.yaml and got back deployment.apps/frontend created
Creating a Service for the Frontend
Before exposing the frontend, I replaced the placeholder with my certificate ARN. I used the same ACM certificate as the API.
Then I exposed it through another load balancer with kubectl apply -f frontend-service.yaml and got back service/frontend created
It’s basically the same as api-service.yaml, just pointed at the frontend Deployment.
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: <namespace-name>
labels:
app: frontend
annotations:
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:region:account-id:certificate/certificate-id"
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 443
targetPort: 8080
type: LoadBalancer
That finished all the deployments and services. I checked everything with kubectl get all -n <namespace-name> and got:
david@dhcp-10-249-202-118 manifests % kubectl get all -n <namespace-name>
NAME READY STATUS RESTARTS AGE
pod/api-579856b9cd-f255n 1/1 Running 0 169m
pod/api-579856b9cd-kgch8 1/1 Running 0 169m
pod/frontend-6f76b694f4-s7q98 1/1 Running 0 28m
pod/frontend-6f76b694f4-w9mmz 1/1 Running 0 28m
pod/mongo-0 1/1 Running 0 173m
pod/mongo-1 1/1 Running 0 172m
pod/mongo-2 1/1 Running 0 172m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/api LoadBalancer 10.100.120.76 af6d6ac51fafc48cc92ac450ac73c1b0-936014577.us-east-1.elb.amazonaws.com 443:31938/TCP 162m
service/frontend LoadBalancer 10.100.175.254 adb0e29e2324d48da8dd64699t-936014577.us-east-1.elb.amazonaws.com 443:32493/TCP 28m
service/mongo ClusterIP None <none> 27017/TCP 172m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/api 2/2 2 2 169m
deployment.apps/frontend 2/2 2 2 28m
NAME DESIRED CURRENT READY AGE
replicaset.apps/api-579856b9cd 2 2 2 169m
replicaset.apps/frontend-6f76b694f4 2 2 2 28m
NAME READY AGE
statefulset.apps/mongo 3/3 173m
If something looks off, double check frontend-deployment.yaml and frontend-service.yaml.
Pointing DNS at the Frontend Load Balancer
Same as the API. I went to Route 53 and gave the frontend load balancer a subdomain on the domain tied to the certificate.

Testing the Voting App
I opened the frontend’s DNS name and the voting app loaded. I could vote for Star Wars, Avatar, or Avengers.
To confirm votes were hitting the database, I opened the mongo-0 pod with kubectl exec -it mongo-0 -n <namespace-name> -- mongo. I switched to the database with use moviedb; and ran db.movies.find().pretty();. The vote counts were going up.

You can also add /movies to the API’s DNS name to see the updated votes.
Wrapping Up
I know not many people will read this, but I’m proud of this project. I learned a lot about Kubernetes, EKS, and AWS. It was fun to build and I’m happy with how it turned out.
Thanks for reading.