Welcome to the ultimate beginner-friendly guide. Whether you're just starting out or want to level up your DevOps game, this tutorial will take you from installing Kubernetes locally all the way to deploying a fullstack Todo app using GitOps via ArgoCD and Helm.
π Why This Guide Matters
Modern development relies on container orchestration, CI/CD, and scalable deployments. Kubernetes and ArgoCD form a powerful duo for achieving that. This tutorial teaches you the basics and advanced steps while deploying a practical project: a fullstack Todo app.
You'll learn:
- How to run Kubernetes locally using
k3d
- How to install and use ArgoCD for GitOps workflows
- How to containerize apps with Docker
- How to create Helm charts for deployments
- How to deploy React and Express in a Kubernetes cluster
Letβs begin!
π οΈ Prerequisites
Make sure you have these installed on your machine:
β Step 1: Set Up Kubernetes Locally with k3d
k3d lets you spin up a lightweight Kubernetes cluster inside Docker containers.
β¨ Create a Cluster
k3d cluster create todo-cluster --servers 1 --agents 2 -p "8080:80@loadbalancer" -p "30080:30080@loadbalancer"
This command:
- Creates a cluster named
todo-cluster
- Sets up 1 server and 2 agents (worker nodes)
- Forwards port 8080 (for app ingress) and 30080 (for ArgoCD)
β Verify the Cluster
kubectl get nodes
You should see 3 nodes listed: 1 control plane and 2 workers.
π Step 2: Install ArgoCD (GitOps Engine)
ArgoCD enables continuous delivery into Kubernetes using Git repositories as the source of truth.
π’ Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
This installs ArgoCD into its own namespace.
π’ Expose the UI
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "NodePort"}}'
kubectl get svc argocd-server -n argocd
Use the listed NodePort (e.g., 30080) to access the UI at:
http://localhost:30080
π Get the Admin Password
kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}" | base64 -d
Use username admin
and this password to log in.
π‘ Step 3: Build the Todo App (React + Express)
Weβll create two folders:
-
backend/
: Express server -
frontend/
: React app
π Backend API
mkdir backend && cd backend
npm init -y
npm install express cors
Create index.js
:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
let todos = [];
app.get('/api/todos', (req, res) => res.json(todos));
app.post('/api/todos', (req, res) => {
const todo = req.body;
todos.push(todo);
res.status(201).json(todo);
});
app.listen(5000, () => console.log('API running on port 5000'));
Add to package.json
:
"scripts": {
"start": "node index.js"
}
π Frontend (React)
cd ../
npx create-react-app frontend
cd frontend
Modify src/App.js
:
import React, { useEffect, useState } from 'react';
function App() {
const [todos, setTodos] = useState([]);
const [text, setText] = useState('');
useEffect(() => {
fetch('/api/todos').then(res => res.json()).then(setTodos);
}, []);
const addTodo = () => {
fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
}).then(res => res.json()).then(todo => setTodos([...todos, todo]));
};
return (
<div>
<h1>Todo App</h1>
<input value={text} onChange={e => setText(e.target.value)} />
<button onClick={addTodo}>Add</button>
<ul>{todos.map((t, i) => <li key={i}>{t.text}</li>)}</ul>
</div>
);
}
export default App;
Add this to frontend/package.json
:
"proxy": "http://localhost:5000"
π¦ Step 4: Dockerize Both Apps
Backend Dockerfile
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
Frontend Dockerfile
FROM node:18 as builder
WORKDIR /app
COPY . .
RUN npm install && npm run build
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
π Step 5: Create Helm Chart
helm create todo-chart
This scaffolds a Helm chart with template files.
Modify values.yaml
to include image info:
backend:
image:
repository: your-dockerhub-user/todo-backend
tag: latest
frontend:
image:
repository: your-dockerhub-user/todo-frontend
tag: latest
In templates/
, create separate deployment & service files for both frontend and backend.
π Step 6: Ingress and Push Images
Build and Push Docker Images
docker build -t your-dockerhub-user/todo-backend ./backend
docker build -t your-dockerhub-user/todo-frontend ./frontend
docker push your-dockerhub-user/todo-backend
docker push your-dockerhub-user/todo-frontend
Install Ingress Controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.1/deploy/static/provider/k3d/deploy.yaml
Ingress YAML (todo-ingress.yaml)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: todo-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: todo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: backend
port:
number: 5000
Add to /etc/hosts
:
127.0.0.1 todo.local
π§ββοΈ Step 7: Deploy with ArgoCD
Push the repo to GitHub.
Then create an ArgoCD Application:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: todo-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/YOUR_USERNAME/todo-app
targetRevision: HEAD
path: todo-chart
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
Apply:
kubectl apply -f argocd-app.yaml
π Step 8: Access the App
Open:
http://todo.local
You should see your live, GitOps-powered, fully deployed React + Express Todo app!
π Next Steps
- Add persistent storage for todos
- Add authentication
- Add CI workflow (GitHub Actions)
- Configure TLS for Ingress
- Split Helm chart into subcharts
β¨ You Did It!
You:
- Ran Kubernetes locally
- Installed and used ArgoCD
- Built and containerized a fullstack app
- Used Helm to manage Kubernetes manifests
- Automated everything via GitOps
High five βοΈ and happy hacking!
Top comments (0)