Deploy a Tetris App to Kubernetes
Deploy a Tetris app to a local Kubernetes cluster with scaling and rolling updates.
Introduction
30 Second Summary
Think about how apps like Netflix or Spotify update without you ever noticing. Behind the scenes, a system manages multiple copies of the app and replaces them one at a time so the service never goes down.
In this project, you will deploy your Tetris Docker image to a local Kubernetes cluster using YAML manifests. You'll scale your app to multiple replicas and perform a rolling update with zero downtime.
What You'll Build
You run a single command and watch Kubernetes pull your Tetris image from Docker Hub, spin up multiple pods, and serve the playable game in your browser.
By the end of this project, you'll have:
- A playable Tetris game in your browser, served by multiple pods inside a local Kubernetes cluster you spun up with Minikube.
- The ability to scale your app from 2 to 4 replicas with a single command and watch new pods spin up in seconds.
- A rolling update from v1.0 to v2.0 where Kubernetes replaces pods one at a time with zero downtime. You'll write the Deployment and NodePort Service manifests yourself.
- Secret Mission: Roll back a bad deployment and add a readiness probe so Kubernetes only sends traffic to healthy pods.
Are there any prerequisites?
This project picks up where Project 1 (Containerize a Tetris App with Docker) left off. You need Docker Desktop installed and running, Homebrew installed, and your tetris-app:v1.0 image pushed to Docker Hub.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters. You're taking the Docker image you pushed to Docker Hub in Project 1 and deploying it to a local Kubernetes cluster using declarative YAML manifests, so Kubernetes manages multiple copies of your app with zero-downtime updates.
Install Minikube and kubectl
You have Docker Desktop running and a Tetris image sitting on Docker Hub. To deploy that image to a Kubernetes cluster, you need two more tools.
Minikube creates a single-node Kubernetes cluster right on your Mac, running inside Docker. kubectl is the command-line tool that sends instructions to any Kubernetes cluster. In this step, you will install both and spin up your first cluster.
In this step, get ready to:
- Install Minikube and kubectl via Homebrew.
- Start a local Kubernetes cluster using the Docker driver.
- Confirm kubectl can talk to your running cluster.
Install Minikube and kubectl via Homebrew
Homebrew makes installing command-line tools straightforward on macOS. Both Minikube and kubectl are available as Homebrew packages.
- Install Minikube by running this command:
brew install minikube
- Install kubectl by running this command:
brew install kubectl
What did you just install?
Minikube is a lightweight tool that runs a single-node Kubernetes cluster inside a Docker container on your machine. It gives you a real cluster to practice with, without needing cloud infrastructure.
kubectl is the standard command-line interface for Kubernetes. Every command you run against a cluster goes through kubectl.
- Verify Minikube installed correctly by running:
minikube version
✔️ I see a version number
You should see something like minikube version: v1.38.1 (or a similar version). Minikube is ready to go.
ⓧ Command not found
If your terminal says the command was not found, Homebrew may not have linked the binary to your PATH.
- Run this to link the Minikube binary:
brew link minikube
- Close your terminal and open a new one, then try minikube version again.
- Verify kubectl installed correctly by running:
kubectl version --client
✔️ I see a version number
You should see client version information printed. kubectl is ready.
ⓧ Command not found
If your terminal says the command was not found, try linking with Homebrew.
- Run this to link the kubectl binary:
brew link kubectl
- Close your terminal and open a new one, then try kubectl version --client again.
Start your Kubernetes cluster
With both tools installed, you can now create a local Kubernetes cluster. Since Docker Desktop is already running, Minikube will use it as the driver to run the cluster inside a container.
- Start your cluster by running this command:
minikube start --driver=docker
What does this command do?
This tells Minikube to create a single-node Kubernetes cluster using Docker as the runtime. Minikube spins up a Docker container that acts as your cluster node. All Kubernetes components run inside that container.
The --driver=docker flag tells Minikube to use Docker Desktop instead of a virtual machine. This is the simplest setup since you already have Docker running.
This command takes a minute or two. You will see status messages as Minikube downloads the base image, creates the container, and starts Kubernetes components.
Seeing an error about Docker not running?
Minikube needs Docker Desktop to be running before it can start. Make sure the Docker Desktop app is open and the Docker engine is active (look for the whale icon in your menu bar).
If you see a message about insufficient resources, check that Docker Desktop has at least 2 CPUs and 2GB of memory allocated in its Settings.
Help me troubleshoot this minikube start error
Confirm kubectl can talk to the cluster
Your cluster is running. Now you need to confirm that kubectl is connected to it and the node is healthy.
- Before you run the next command, predict what you expect to see. How many nodes should a single-node cluster have?
- Check your cluster node by running:
kubectl get nodes
You should see one node named minikube with a STATUS of Ready. This confirms your cluster is healthy and kubectl can communicate with it.
Node showing NotReady?
If the STATUS shows NotReady, the cluster is still starting up. Wait 30 seconds and run kubectl get nodes again.
If the command hangs or times out, Minikube may not have started successfully. Run minikube status to check whether all components are running.
Help me figure out why my cluster node is not Ready
Your local Kubernetes cluster is up and running. Next up, you will write a Deployment manifest that tells Kubernetes to pull your Tetris image from Docker Hub and run it as pods inside this cluster.
Deploy Your Tetris App to the Cluster
Your cluster is up and kubectl is connected. But the cluster is completely empty. Kubernetes won't do anything until you tell it what to run.
You tell Kubernetes what to run by writing a Deployment manifest. This is a YAML file that describes your desired state: which image to pull, how many copies to run, and how to label them. Kubernetes reads it and makes it happen.
In this step, get ready to:
- Write a Deployment manifest that tells Kubernetes to run 2 replicas of your Tetris container.
- Apply the manifest and watch pods transition to Running.
- Discover that running pods does not mean the app is accessible from your browser.
Write the Deployment YAML
A Deployment is a Kubernetes resource that manages a set of identical pods. You describe your desired state in a YAML file, and Kubernetes continuously works to make reality match that description.
- Move to your Desktop and create a project folder by running these commands:
cd ~/Desktop
mkdir tetris-k8s
cd tetris-k8s
- Confirm you're in the new folder by running:
pwd
You should see a path ending in tetris-k8s.
- Create deployment.yaml with the following content:
apiVersion: apps/v1
kind: Deployment
metadata:
name: tetris-deployment
labels:
app: tetris
spec:
replicas: 2
selector:
matchLabels:
app: tetris
template:
metadata:
labels:
app: tetris
spec:
containers:
- name: tetris
image: [[DOCKERHUB_USERNAME="your-dockerhub-username"]]/tetris-app:v1.0
ports:
- containerPort: 80
What does this YAML do?
- apiVersion: apps/v1 and kind: Deployment tell Kubernetes you want a Deployment resource.
- replicas: 2 means Kubernetes will create and maintain exactly 2 identical pods.
- selector and template.metadata.labels must match. This is how the Deployment knows which pods belong to it.
- image points to the Docker Hub image you pushed in Project 1. Replace the username with your actual Docker Hub username.
- containerPort: 80 declares that the container listens on port 80 (nginx inside your Tetris image).
- Save deployment.yaml.
- Confirm the file exists by running:
ls
You should see deployment.yaml listed in the output.
Don't see deployment.yaml?
- Make sure you saved the file inside the tetris-k8s folder. Run pwd to confirm your terminal is in the right directory.
- If you used a text editor, double-check the filename is exactly deployment.yaml with no extra extension like .txt.
Help me troubleshoot my missing deployment.yaml file
✔️ Awesome, I've got everything!
Great. Double check you've saved deployment.yaml and you're ready to deploy.
ⓧ I'd like to double check the full code
apiVersion: apps/v1
kind: Deployment
metadata:
name: tetris-deployment
labels:
app: tetris
spec:
replicas: 2
selector:
matchLabels:
app: tetris
template:
metadata:
labels:
app: tetris
spec:
containers:
- name: tetris
image: your-dockerhub-username/tetris-app:v1.0
ports:
- containerPort: 80
Apply the Deployment and watch pods start
Your manifest describes the desired state. Now you need to hand it to Kubernetes so it can make it real.
- Apply your Deployment to the cluster by running:
kubectl apply -f deployment.yaml
You should see:
deployment.apps/tetris-deployment created
What does kubectl apply do?
The kubectl apply -f command sends your YAML to the Kubernetes API server. The server reads it, creates a Deployment object, and immediately starts working to make reality match your spec. It spins up a ReplicaSet, which in turn creates the requested number of pods.
- Watch your pods come to life by running:
kubectl get pods
You might see the STATUS column show ContainerCreating at first. This means Kubernetes is pulling your image from Docker Hub. Wait a few seconds and run the command again.
Once both pods are ready, you should see output like this:
NAME READY STATUS RESTARTS AGE
tetris-deployment-xxxxxxxxxx-xxxxx 1/1 Running 0 30s
tetris-deployment-xxxxxxxxxx-xxxxx 1/1 Running 0 30s
Both pods show STATUS Running and READY 1/1. Your Tetris containers are alive inside the cluster.
Pods stuck on ContainerCreating?
- Make sure Docker Desktop is running. Minikube needs it to pull images.
- Verify your Docker Hub username in deployment.yaml matches the one you used in Project 1 when you pushed tetris-app:v1.0.
- Run kubectl describe pods and look at the Events section at the bottom for error messages about image pulls.
Help me debug why my pods are stuck on ContainerCreating
Try to access the app
Both pods are Running. Your Tetris game is alive inside the cluster. Before you do anything else, think about this:
Do you think you can play the game at http://localhost:80 in your browser right now? Why or why not?
- Open your browser and navigate to http://localhost:80.
The page does not load. You see a connection error.
Why can't I reach the game?
Remember Project 1? Your Docker container was also unreachable until you added the -p 80:80 port mapping flag. The same isolation applies here, but at a bigger scale.
Pods run inside the cluster's internal network. Your Mac has no direct route to that network. On top of that, Minikube with the Docker driver runs the entire cluster inside a Docker container, adding another layer of network isolation.
You need a Kubernetes Service to bridge the gap between your Mac and the pods. That's the next step.
Your pods are running, but they're trapped behind the cluster network. Next up, you'll create a Service that punches a hole through that wall and lets you play the game in your browser.
Expose the App with a NodePort Service
In the last step, you saw that your pods are running but completely unreachable from your browser. The cluster has its own internal network, and pods live inside it.
A Kubernetes Service solves this by creating a stable network endpoint that routes traffic to your pods. In this step, you'll create a NodePort Service that opens a port on the cluster node and tunnels traffic through to your Tetris containers.
In this step, get ready to:
- Write a service.yaml manifest that targets your Tetris pods by label.
- Apply the Service and access the Tetris game in your browser.
- Inspect the Service routing to understand how traffic flows to your pods.
Write the Service YAML
Just like your Deployment, a Service is defined in a YAML manifest. The Service uses a selector to find pods with a matching label, then routes traffic to them.
- Create a new file called service.yaml in the same folder as your deployment.yaml by running:
touch service.yaml
- Open service.yaml in your editor and paste the following:
apiVersion: v1
kind: Service
metadata:
name: tetris-service
labels:
app: tetris
spec:
type: NodePort
selector:
app: tetris
ports:
- protocol: TCP
port: 80
targetPort: 80
What does this manifest do?
- type: NodePort tells Kubernetes to open a port on the cluster node (allocated from range 30000-32767) and forward traffic into the Service.
- selector: app: tetris matches your pods. This is the same label you set in deployment.yaml. The Service finds pods by this label and routes traffic to them.
- port: 80 is the port the Service listens on. targetPort: 80 is the port on the pod where nginx is running inside your container.
- Save service.yaml.
- Confirm the file exists by running:
ls
You should see both deployment.yaml and service.yaml listed.
Don't see service.yaml?
Make sure you created the file in the same directory where deployment.yaml lives. Run pwd to check your current directory.
Help me find my service.yaml file
✔️ Awesome, I've got everything!
Great. Make sure you've saved service.yaml before moving on.
ⓧ I'd like to double check the full code
apiVersion: v1
kind: Service
metadata:
name: tetris-service
labels:
app: tetris
spec:
type: NodePort
selector:
app: tetris
ports:
- protocol: TCP
port: 80
targetPort: 80
Apply the Service and access the game
Your Service manifest is ready. Now you'll apply it to the cluster and use Minikube's tunnel to reach the game from your browser.
- Apply the Service to your cluster by running:
kubectl apply -f service.yaml
You should see service/tetris-service created in the output.
Why do you need a tunnel on macOS?
When using the Docker driver on macOS, the node IP is not directly reachable from your Mac. Minikube creates an SSH tunnel that maps a localhost port to the NodePort inside the cluster.
The terminal running the tunnel must stay open for the connection to work. If you close it, the URL stops working.
- Open a new terminal tab or window. You need a separate terminal because the tunnel command keeps running.
- In the new terminal, start the tunnel by running:
minikube service tetris-service --url
This prints a URL like http://127.0.0.1:57123 (your port number will differ). The terminal stays occupied maintaining the tunnel.
- Copy the printed URL.
- Paste it into your browser's address bar and press Enter.
Before you look at the result, what do you expect to see given that the Service is now routing traffic to your pods?
You should see the Tetris game load and be fully playable in your browser. This is the same game from your Docker image, now served by Kubernetes pods.
Game not loading?
- Make sure the terminal running minikube service tetris-service --url is still open. Closing it kills the tunnel.
- Check that your pods are still running with kubectl get pods in your original terminal. Both pods should show STATUS Running.
- Verify the Service exists with kubectl get services. You should see tetris-service with type NodePort.
Help me debug why my Tetris game isn't loading
Inspect the Service and understand the routing
The game is running, but it helps to understand how traffic actually reaches your pods. Two commands show you the full picture.
- Switch back to your original terminal (not the tunnel terminal).
- List all Services in the cluster by running:
kubectl get services
You should see a table with columns NAME, TYPE, CLUSTER-IP, EXTERNAL-IP, PORT(S), and AGE. Your tetris-service appears with type NodePort and a PORT(S) value like 80:3XXXX/TCP showing the Service port and the allocated NodePort.
- Get detailed routing information by running:
kubectl describe service tetris-service
What does this output tell you?
- Selector: app=tetris confirms which pods the Service targets.
- Type: NodePort confirms external access is enabled.
- Port and TargetPort show traffic flowing from port 80 on the Service to port 80 on each pod.
- Endpoints lists the internal IP addresses of your 2 running pods. This proves the Service discovered your pods using the label selector.
The Endpoints line is the key proof that everything is connected. If you see two IP:port entries there, traffic from your browser is being load-balanced across both pods.
Your Tetris game is live and served by Kubernetes pods. Next up, you'll scale your deployment to 4 replicas and perform a rolling update to a new image version with zero downtime.
Scale and Perform a Rolling Update
Your Tetris game is live in the browser, served from pods that Kubernetes manages for you. That's a solid foundation.
Production apps need to handle load spikes and receive updates without going offline. Kubernetes handles both through your Deployment manifest. You change the YAML, re-apply it, and Kubernetes figures out how to get from the current state to the desired state.
In this step, get ready to:
- Scale your Deployment from 2 to 4 replicas.
- Perform a rolling update from v1.0 to v2.0 with zero downtime.
- Inspect rollout history to see both revisions.
Scale to 4 replicas
Right now your Deployment runs 2 replicas. Scaling up means changing a single number in your YAML and re-applying it. Kubernetes creates the additional pods automatically.
- In deployment.yaml, find this line:
replicas: 2
- Change 2 to 4, so the line now looks like this:
replicas: 4
- Save deployment.yaml.
- Apply the updated manifest by running:
kubectl apply -f deployment.yaml
- Watch the new pods appear by running:
kubectl get pods
You should see 4 pods listed. Two will show STATUS Running immediately, and the two new ones will transition from ContainerCreating to Running within a few seconds.
What just happened?
You told Kubernetes you want 4 replicas instead of 2. Kubernetes compared the desired state (4) against the current state (2), calculated the difference, and created 2 new pods to close the gap. The existing pods were untouched.
Still seeing only 2 pods?
Make sure you saved deployment.yaml before running kubectl apply. Unsaved changes are not picked up.
Double-check that the replicas field sits under spec: (not under template:). Indentation matters in YAML.
help me debug why my pods didn't scale up
Perform a rolling update to v2.0
A rolling update replaces pods one at a time so the app stays available throughout the process. You trigger one by changing the image tag in your Deployment YAML.
- In deployment.yaml, find this line:
image: [[DOCKERHUB_USERNAME="your-dockerhub-username"]]/tetris-app:v1.0
- Change v1.0 to v2.0, so the line now looks like this:
image: [[DOCKERHUB_USERNAME="your-dockerhub-username"]]/tetris-app:v2.0
- Save deployment.yaml.
- Apply the change by running:
kubectl apply -f deployment.yaml
- Watch the rolling update in real time by running:
kubectl rollout status deployment/tetris-deployment
You'll see progress messages as Kubernetes replaces pods one by one. When it finishes, the final line reads:
deployment "tetris-deployment" successfully rolled out
What does zero downtime mean here?
Kubernetes never terminates all old pods at once. It spins up a new pod with the v2.0 image, waits for it to become Ready, then terminates one old pod. This repeats until every pod runs v2.0.
Because the Service selects pods by the app: tetris label (not by image version), it routes traffic to whichever pods are Ready at any moment. Players never see an outage.
✔️ Awesome, I've got everything!
Great. Double check you've saved deployment.yaml with both changes (replicas 4 and image v2.0).
ⓧ I'd like to double check the full code
apiVersion: apps/v1
kind: Deployment
metadata:
name: tetris-deployment
labels:
app: tetris
spec:
replicas: 4
selector:
matchLabels:
app: tetris
template:
metadata:
labels:
app: tetris
spec:
containers:
- name: tetris
image: [[DOCKERHUB_USERNAME="your-dockerhub-username"]]/tetris-app:v2.0
ports:
- containerPort: 80
Rollout stuck or showing errors?
If you see ImagePullBackOff, Kubernetes cannot pull the v2.0 image from Docker Hub. Confirm you pushed tetris-app:v2.0 in Project 1's Secret Mission. If you haven't, you can tag your existing image as v2.0 and push it.
If the rollout seems frozen, check pod events by running kubectl describe pod on one of the new pods. The Events section at the bottom shows exactly what went wrong.
help me fix my stuck rolling update
Verify the update and check rollout history
Before you run the next commands, predict: how many revisions do you think the rollout history will show? Remember, you've applied this Deployment twice now (once with v1.0, once with v2.0).
- Confirm all 4 pods are running v2.0 by running:
kubectl get pods
You should see 4 pods, all with STATUS Running and a recent AGE on each.
- Inspect the rollout history by running:
kubectl rollout history deployment/tetris-deployment
You'll see two revisions listed. Revision 1 is your original v1.0 deployment. Revision 2 is the v2.0 update you just applied.
What does rollout history show?
The output lists a REVISION column and a CHANGE-CAUSE column. Each time you change the pod template (like updating the image tag), Kubernetes records a new revision. This history is what powers the kubectl rollout undo command you'll use in the Secret Mission.
- Confirm the rollout completed successfully by running:
kubectl rollout status deployment/tetris-deployment
Since the rollout already finished, you'll immediately see:
deployment "tetris-deployment" successfully rolled out
Seeing fewer than 4 pods running?
Run kubectl get pods again and check the STATUS column. If any pod shows ErrImagePull or CrashLoopBackOff, the image could not be pulled or the container crashed. Verify your Docker Hub image name matches exactly what you pushed in Project 1.
help me troubleshoot my deployment pods
You just scaled a Deployment and performed a zero-downtime rolling update. These are the same operations teams use in production to handle traffic spikes and ship new code without interrupting users. Next up, the Secret Mission challenges you to roll back a bad deploy and add health checks.
Secret mission
Rollback and Readiness Probe
What separates a working deployment from a production-ready one? Your deployment can update, but what if a bad image gets pushed? And how does Kubernetes know a pod is actually ready to serve traffic before sending requests to it?
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. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- Minikube cluster running locally (uses Docker Desktop CPU and memory).
- Docker images cached inside Minikube (local disk space).
- Local project files (deployment.yaml and service.yaml).
- Minikube and kubectl installed via Homebrew.
Keep everything running
No action needed. Choose this if you are still actively building or plan to continue with the next project in this series.
- Your Minikube cluster continues running and your pods remain accessible.
- Docker Desktop must remain open for the cluster to stay alive.
- Minikube and kubectl remain installed on your machine.
Pause - I'll come back to this later
Shut down the cluster to free up CPU and memory. Your cluster state is preserved and you can restart it later.
- Stop the Minikube cluster by running:
minikube stop
- When you are ready to resume, start it again by running:
minikube start
Your Deployments, Services, and pods will be restored automatically.
Delete - I don't want to use this again
Remove all project resources and start fresh. This deletes your local cluster, all pods, services, and cached images.
- Delete the Minikube cluster by running:
minikube delete
What does this remove?
This removes the cluster, all pods, services, and Docker images cached inside Minikube. Your Docker Hub images remain untouched.
- Delete your local project files by running:
rm deployment.yaml service.yaml
- Uninstall Minikube and kubectl by running:
brew uninstall minikube
brew uninstall kubectl
Nice Work!
Nice Work!
You just deployed a Tetris app to a local Kubernetes cluster, scaled it, and performed a rolling update with zero downtime. That is the same deployment workflow used in production environments everywhere.
You've learned how to:
- Written a Deployment manifest from scratch that pulls your Tetris image from Docker Hub and runs multiple replicas as pods inside a local Minikube cluster.
- Created a NodePort Service to expose network-isolated pods to your browser so you can play the game served from Kubernetes.
- Scaled your deployment from 2 to 4 replicas and performed a rolling update from v1.0 to v2.0 with zero downtime.
- Secret Mission: Rolled back a deployment and added a readiness probe for production-grade health checking.
Ready to quiz yourself?