Skip to content

Commit c1e6d10

Browse files
committed
Add terrafrom EKS infrastructure and updated the pipeline
1 parent 4ea1310 commit c1e6d10

2 files changed

Lines changed: 117 additions & 33 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ jobs:
7171
- name: Checkout code
7272
uses: actions/checkout@v4
7373

74+
<<<<<<< HEAD
7475
- name: Configure AWS Credentials
7576
uses: aws-actions/configure-aws-credentials@v2
7677
with:
@@ -84,12 +85,19 @@ jobs:
8485
- name: Configure kubeconfig
8586
run: |
8687
aws eks update-kubeconfig --region ${{ secrets.AWS_REGION }} --name ${{ secrets.EKS_CLUSTER_NAME }}
88+
=======
89+
- name: Create k8s cluster with kind
90+
uses: helm/kind-action@v1.10.0
91+
with:
92+
cluster_name: todo-cluster
93+
>>>>>>> 95e164da5c6c68e5e8538cb95d411f66e6aa9f27
8794

8895
- name: Update image tags
8996
run: |
9097
sed -i "s|image: ${{ secrets.DOCKERHUB_USERNAME }}/server:.*|image: ${{ secrets.DOCKERHUB_USERNAME }}/server:${{ github.run_number }}|" k8s/backend/deployment.yaml
9198
sed -i "s|image: ${{ secrets.DOCKERHUB_USERNAME }}/client:.*|image: ${{ secrets.DOCKERHUB_USERNAME }}/client:${{ github.run_number }}|" k8s/frontend/deployment.yaml
9299
100+
<<<<<<< HEAD
93101
- name: Deploy to EKS Cluster
94102
run: |
95103
kubectl apply -f k8s/postgres/secret.yaml
@@ -98,8 +106,37 @@ jobs:
98106
kubectl apply -f k8s/frontend/deployment.yaml
99107
kubectl rollout status deployment/backend --timeout=300s
100108
kubectl rollout status deployment/frontend --timeout=300s
109+
=======
110+
- name: Deploy to kind
111+
run: |
112+
kubectl apply --validate=false -f k8s/postgres/secret.yaml
113+
kubectl apply --validate=false -f k8s/postgres/deployment.yaml
114+
kubectl apply --validate=false -f k8s/backend/deployment.yaml
115+
kubectl apply --validate=false -f k8s/frontend/deployment.yaml
116+
117+
- name: Wait for pods
118+
run: |
119+
kubectl wait --for=condition=ready pod -l app=postgres --timeout=120s
120+
121+
# Wait for postgres to actually accept connections
122+
kubectl exec $(kubectl get pod -l app=postgres -o jsonpath="{.items[0].metadata.name}") -- sh -c "until pg_isready -h 127.0.0.1 -U postgres; do echo 'waiting for postgres...'; sleep 2; done"
123+
124+
# Now create the table
125+
kubectl exec $(kubectl get pod -l app=postgres -o jsonpath="{.items[0].metadata.name}") -- psql -h 127.0.0.1 -U postgres -d todo_db -c "CREATE TABLE IF NOT EXISTS todo (todo_id SERIAL PRIMARY KEY, description VARCHAR(255));"
126+
127+
kubectl wait --for=condition=ready pod -l app=backend --timeout=120s
128+
kubectl wait --for=condition=ready pod -l app=frontend --timeout=120s
129+
>>>>>>> 95e164da5c6c68e5e8538cb95d411f66e6aa9f27
101130

102131
- name: Verify deployment
103132
run: |
104133
kubectl get pods
105-
kubectl get services
134+
<<<<<<< HEAD
135+
kubectl get services
136+
=======
137+
kubectl get services
138+
# Test the API works
139+
kubectl port-forward service/frontend 8080:80 &
140+
sleep 5
141+
curl http://localhost:8080/api/todos
142+
>>>>>>> 95e164da5c6c68e5e8538cb95d411f66e6aa9f27

README.md

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 📋 PERN Task Manager — GitHub Actions CI/CD Pipeline
1+
# PERN Task Manager — GitHub Actions CI/CD Pipeline
22

33
![CI/CD](https://img.shields.io/badge/CI%2FCD-GitHub_Actions-2088FF?style=for-the-badge&logo=githubactions&logoColor=white)
44
![Kubernetes](https://img.shields.io/badge/Kubernetes-326CE5?style=for-the-badge&logo=kubernetes&logoColor=white)
@@ -9,22 +9,17 @@
99
![Node.js](https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)
1010
![Nginx](https://img.shields.io/badge/Nginx-009639?style=for-the-badge&logo=nginx&logoColor=white)
1111

12+
<<<<<<< HEAD
1213
A full-stack PERN (PostgreSQL, Express, React, Node.js) task manager app deployed to Kubernetes via a fully automated CI/CD pipeline using GitHub Actions, kind, and K3s.
1314

1415
> **v3 of this project** — previously deployed with docker compose. Migrated to Kubernetes to move beyond single-host deployments. See [task-manager-cicd-pipeline](https://github.com/kithupag/task-manager-cicd-pipeline) for the Jenkins version.
16+
=======
17+
A full-stack PERN (PostgreSQL, Express, React, Node.js) task manager app with two deployment modes — Docker Compose on AWS EC2 and Kubernetes via kind/k3s — both automated through GitHub Actions.
1518

16-
---
17-
18-
## 🏗️ Architecture
19+
> **v2 of this project** — previously deployed with Jenkins. Migrated to GitHub Actions and extended with Kubernetes support. See [task-manager-cicd-pipeline](https://github.com/kithupag/task-manager-cicd-pipeline) for the Jenkins version.
20+
>>>>>>> 95e164da5c6c68e5e8538cb95d411f66e6aa9f27
1921
20-
```
21-
Developer → GitHub Push → GitHub Actions → Docker Hub → AWS EC2
22-
23-
Build & push Docker images
24-
Pin exact build number tag in compose
25-
SCP docker-compose.yaml to EC2
26-
SSH into EC2 → docker compose up
27-
```
22+
---
2823

2924
### Infrastructure
3025

@@ -40,8 +35,9 @@ Developer → GitHub Push → GitHub Actions → Docker Hub → AWS EC2
4035

4136
---
4237

43-
## 🚀 Pipeline Stages
38+
## Pipeline Stages
4439

40+
### Mode 1 — Docker Compose (EC2)
4541
```
4642
build-and-push ──────────────────────────► deploy
4743
├── Checkout code ├── Checkout code
@@ -50,11 +46,24 @@ build-and-push ─────────────────────
5046
└── Build & push server image └── SSH → docker compose up -d
5147
```
5248

49+
### Mode 2 — Kubernetes (kind)
50+
```
51+
build-and-push ──────────────────────────► deploy
52+
├── Checkout code ├── Checkout code
53+
├── Log in to Docker Hub ├── Create kind cluster
54+
├── Build & push client image ├── Update image tags
55+
└── Build & push server image ├── kubectl apply all manifests
56+
├── Wait for postgres ready
57+
├── Create database table
58+
├── Wait for backend + frontend
59+
└── Run API smoke tests
60+
```
61+
5362
The `deploy` job has `needs: build-and-push` — it only runs if the build succeeds. Both jobs run on fresh GitHub-hosted Ubuntu VMs.
5463

5564
---
5665

57-
## 📁 Project Structure
66+
## Project Structure
5867

5968
```
6069
task-manager-github-actions/
@@ -81,14 +90,23 @@ task-manager-github-actions/
8190
│ ├── Dockerfile
8291
│ └── package.json
8392
93+
├── k8s/ # Kubernetes manifests
94+
│ ├── postgres/
95+
│ │ ├── deployment.yaml # Postgres Deployment + Service + PVC
96+
│ │ └── secret.yaml # DB credentials as k8s Secret
97+
│ ├── backend/
98+
│ │ └── deployment.yaml # Backend Deployment + Service
99+
│ └── frontend/
100+
│ └── deployment.yaml # Frontend Deployment + NodePort Service
101+
84102
├── database.sql # Mounted into postgres on fresh deploy
85-
├── docker-compose.yaml # Production compose — image tags pinned by pipeline
103+
├── docker-compose.yaml # Local dev + EC2 compose deployment
86104
└── README.md
87105
```
88106

89107
---
90108

91-
## 🔧 Key Technical Decisions
109+
## Key Technical Decisions
92110

93111
**GitHub Actions over Jenkins**
94112
Jenkins requires a dedicated server running 24/7. GitHub Actions runs on GitHub's infrastructure — no server to maintain, no Docker socket to mount, no SSH keys to manage inside a container. The pipeline logic is identical, the operational overhead is zero.
@@ -120,7 +138,7 @@ All three containers report real status. Backend and frontend are checked via HT
120138

121139
---
122140

123-
## 🔄 Comparison with Jenkins Version
141+
## Comparison with Jenkins Version
124142

125143
| | Jenkins Version | GitHub Actions Version |
126144
|--|----------------|----------------------|
@@ -135,7 +153,7 @@ All three containers report real status. Backend and frontend are checked via HT
135153

136154
---
137155

138-
## 🛠️ Local Development Setup
156+
## Local Development Setup
139157

140158
### Prerequisites
141159
- Docker & Docker Compose
@@ -169,7 +187,7 @@ DB_NAME=todo_db
169187

170188
---
171189

172-
## 📡 API Endpoints
190+
## API Endpoints
173191

174192
| Method | Endpoint | Description |
175193
|--------|----------|-------------|
@@ -181,7 +199,7 @@ DB_NAME=todo_db
181199

182200
---
183201

184-
## ⚙️ Replicating This Pipeline
202+
## Replicating This Pipeline
185203

186204
### GitHub Secrets Required
187205

@@ -222,7 +240,23 @@ mkdir -p ~/task-manager
222240

223241
---
224242

225-
## 💡 Lessons Learned
243+
## Kubernetes Concepts Used
244+
245+
**Deployment** — manages desired pod state. If a pod crashes Kubernetes automatically replaces it.
246+
247+
**Service** — provides stable DNS names (, , ) so pods can reach each other regardless of their changing IPs.
248+
249+
**PersistentVolumeClaim** — requests persistent storage for postgres so data survives pod restarts.
250+
251+
**Secret** — stores database credentials as base64-encoded values injected as environment variables — never hardcoded in manifests.
252+
253+
**NodePort Service** — exposes the frontend on port 30080 on every node, accessible from outside the cluster.
254+
255+
**Readiness + Liveness Probes** — readiness controls when traffic is sent to a pod, liveness restarts pods that stop responding. Both use HTTP checks.
256+
257+
---
258+
259+
## Lessons Learned
226260

227261
**From migrating Jenkins → GitHub Actions:**
228262
- Pipeline concepts are identical across tools — triggers, jobs, steps, secrets. Learning one makes the next trivial.
@@ -231,29 +265,42 @@ mkdir -p ~/task-manager
231265
- `needs:` in GitHub Actions is more explicit than Jenkins stage ordering — you declare job dependencies intentionally.
232266
- A running container might be from an old image — always verify the tag matches your latest build number before debugging.
233267

268+
**From migrating to Kubernetes:**
269+
- `kubectl apply` succeeds even if the pod crashes seconds later — always verify with `kubectl wait`
270+
- Pod ready does not mean application ready — use `pg_isready` not just Kubernetes readiness probes
271+
- `-it` flags don't work in CI pipelines — no TTY available, always remove from `kubectl exec` in automation
272+
- Kubernetes service names are DNS — containers reach each other by service name, not IP
273+
- `kubectl describe pod` is more useful than `kubectl logs` when a pod won't start
274+
234275
**Carried over from Jenkins version:**
235276
- Always use relative URLs in React — `localhost` in fetch calls breaks in production
236277
- `docker compose ps` showing `Up` is not the same as healthy — always add health checks
237278
- Env vars with duplicate keys in JS objects silently use the last value — never hardcode credentials
238279

239280
---
240281

241-
## 📌 Improvements
242-
243-
### ✅ Completed
244-
- [x] Migrated from Jenkins to GitHub Actions
282+
### Completed
283+
- [x] Migrated from Jenkins to GitHub Actions — zero CI server overhead
245284
- [x] Automated database table creation via `docker-entrypoint-initdb.d/`
246285
- [x] Docker health checks on all services
247286
- [x] Pinned image tags — exact build number deployed, never `:latest`
248-
249-
### 🔜 Up Next
250-
- [ ] Provision EC2 infrastructure with Terraform
287+
- [x] Kubernetes manifests for all three services
288+
- [x] CI pipeline deploys to real kind cluster on every push
289+
- [x] Readiness and liveness probes on all pods
290+
- [x] Secrets management via Kubernetes Secrets
291+
- [x] Persistent storage for database via PVC
292+
- [x] Automated API smoke tests after deployment
293+
294+
### Up Next
295+
- [ ] Helm charts — package manifests with configurable values
296+
- [ ] Multiple environments — dev, staging, production namespaces
297+
- [ ] Horizontal Pod Autoscaler
298+
- [ ] Provision EKS cluster with Terraform
251299
- [ ] Add security scanning — Trivy, Snyk, Checkov
252-
- [ ] Add Prometheus + Grafana monitoring
253-
- [ ] Migrate to Kubernetes deployment
300+
- [ ] Set up Prometheus + Grafana monitoring
254301

255302
---
256303

257-
## 📄 License
304+
## License
258305

259-
MIT
306+
MIT

0 commit comments

Comments
 (0)