Skip to content

Commit 4a001bb

Browse files
authored
Revise README for deployment modes and Terraform integration
Updated README to reflect new deployment modes and infrastructure changes, including Terraform and AWS EKS.
1 parent 95e164d commit 4a001bb

1 file changed

Lines changed: 79 additions & 12 deletions

File tree

README.md

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
# 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)
4+
![Terraform](https://img.shields.io/badge/Terraform-7B42BC?style=for-the-badge&logo=terraform&logoColor=white)
45
![Kubernetes](https://img.shields.io/badge/Kubernetes-326CE5?style=for-the-badge&logo=kubernetes&logoColor=white)
6+
![AWS EKS](https://img.shields.io/badge/AWS_EKS-FF9900?style=for-the-badge&logo=amazonaws&logoColor=white)
57
![Docker](https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white)
6-
![AWS](https://img.shields.io/badge/AWS_EC2-FF9900?style=for-the-badge&logo=amazonaws&logoColor=white)
78
![PostgreSQL](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white)
89
![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB)
910
![Node.js](https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)
1011
![Nginx](https://img.shields.io/badge/Nginx-009639?style=for-the-badge&logo=nginx&logoColor=white)
1112

12-
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.
13+
A full-stack PERN (PostgreSQL, Express, React, Node.js) task manager app with three deployment modes — Docker Compose on EC2, Kubernetes via kind/k3s, and Kubernetes on AWS EKS provisioned entirely with Terraform — all automated through GitHub Actions.
1314

14-
> **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.
15+
> **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/yourusername/task-manager-cicd-pipeline) for the Jenkins version.
1516
1617
---
1718

@@ -22,8 +23,10 @@ A full-stack PERN (PostgreSQL, Express, React, Node.js) task manager app with tw
2223
| Source Control | GitHub |
2324
| CI/CD | GitHub Actions |
2425
| Image Registry | Docker Hub |
25-
| Production Server | AWS EC2 (Amazon Linux 2023) |
26-
| Database | PostgreSQL 16 (Docker) |
26+
| Infrastructure as Code | Terraform |
27+
| Cloud | AWS (VPC, EKS, S3, DynamoDB) |
28+
| Container Orchestration | Kubernetes (kind, k3s, EKS) |
29+
| Database | PostgreSQL 16 |
2730
| Backend | Node.js + Express |
2831
| Frontend | React + Nginx |
2932

@@ -53,6 +56,20 @@ build-and-push ─────────────────────
5356
└── Run API smoke tests
5457
```
5558

59+
### Mode 3 — Terraform + EKS
60+
```
61+
build-and-push ──► provision ──────────────────────► deploy
62+
├── Checkout ├── Checkout ├── Checkout
63+
├── Login ├── Configure AWS credentials ├── Configure AWS credentials
64+
├── Build client ├── Setup Terraform ├── Setup kubectl
65+
└── Build server ├── terraform init ├── aws eks update-kubeconfig
66+
├── terraform plan ├── Update image tags
67+
└── terraform apply ├── kubectl apply manifests
68+
(VPC + EKS + node groups) └── kubectl rollout status
69+
```
70+
71+
The `provision` job only runs when files in `infra/` change — app deployments skip straight to `deploy`. The `deploy` job uses `aws eks update-kubeconfig` instead of a stored kubeconfig secret.
72+
5673
The `deploy` job has `needs: build-and-push` — it only runs if the build succeeds. Both jobs run on fresh GitHub-hosted Ubuntu VMs.
5774

5875
---
@@ -93,6 +110,14 @@ task-manager-github-actions/
93110
│ └── frontend/
94111
│ └── deployment.yaml # Frontend Deployment + NodePort Service
95112
113+
├── infra/ # Terraform infrastructure as code
114+
│ ├── bootstrap/
115+
│ │ └── main.tf # One-time: creates S3 state bucket + DynamoDB lock
116+
│ ├── main.tf # VPC, subnets, EKS cluster, security groups
117+
│ ├── variables.tf # Input variables
118+
│ ├── outputs.tf # Cluster name, endpoint
119+
│ └── backend.tf # Remote state in S3
120+
96121
├── database.sql # Mounted into postgres on fresh deploy
97122
├── docker-compose.yaml # Local dev + EC2 compose deployment
98123
└── README.md
@@ -114,6 +139,30 @@ location /api {
114139
}
115140
```
116141

142+
**Terraform provisions all AWS infrastructure**
143+
The entire AWS environment — VPC, subnets, internet gateway, route tables, security groups, IAM roles, and the EKS cluster — is defined as code in `infra/main.tf`. A fresh AWS account can go from zero to a running EKS cluster with a single `terraform apply`. `terraform destroy` removes everything with no manual cleanup.
144+
145+
**Remote state with S3 + DynamoDB locking**
146+
Terraform state lives in S3 so both local machines and GitHub Actions runners read and write the same state. DynamoDB provides state locking — prevents two pipeline runs from modifying infrastructure simultaneously and corrupting state.
147+
148+
```hcl
149+
terraform {
150+
backend "s3" {
151+
bucket = "myapp-terraform-state"
152+
key = "eks/terraform.tfstate"
153+
region = "ap-southeast-1"
154+
dynamodb_table = "terraform-state-lock"
155+
encrypt = true
156+
}
157+
}
158+
```
159+
160+
**Bootstrap pattern for state infrastructure**
161+
The S3 bucket and DynamoDB table can't be managed by the same Terraform config that uses them — a chicken-and-egg problem. A separate `infra/bootstrap/` config creates them once manually. Everything else is managed by CI/CD.
162+
163+
**Terraform only runs when infrastructure changes**
164+
The `provision` job uses a path filter — it only triggers when files in `infra/` are modified. Pushing app code changes skips Terraform entirely and goes straight to deployment. This prevents unnecessary EKS reprovisioning on every commit.
165+
117166
**Build number pinning**
118167
The pipeline uses `sed` to replace image tags in `docker-compose.yaml` with the exact GitHub run number before deploying. Every production deployment references a specific immutable image — never `:latest`. Rollback is changing one number.
119168

@@ -201,11 +250,15 @@ Go to **Settings → Secrets and variables → Actions** and add:
201250

202251
| Secret | Value |
203252
|--------|-------|
204-
| `DOCKERHUB_USERNAME` | Your Docker Hub username |
205-
| `DOCKERHUB_TOKEN` | Docker Hub access token (not password) |
206-
| `EC2_HOST` | EC2 public IP or Elastic IP |
207-
| `EC2_USER` | `ec2-user` |
208-
| `EC2_SSH_KEY` | Private key contents (PEM format) |
253+
| `DOCKERHUB_USERNAME` | Docker Hub username |
254+
| `DOCKERHUB_TOKEN` | Docker Hub access token |
255+
| `EC2_HOST` | EC2 public IP (docker compose mode) |
256+
| `EC2_USER` | `ec2-user` (docker compose mode) |
257+
| `EC2_SSH_KEY` | Private key PEM (docker compose mode) |
258+
| `AWS_ACCESS_KEY_ID` | IAM user access key (Terraform + EKS mode) |
259+
| `AWS_SECRET_ACCESS_KEY` | IAM user secret key (Terraform + EKS mode) |
260+
| `AWS_REGION` | e.g. `ap-southeast-1` |
261+
| `EKS_CLUSTER_NAME` | `myapp-eks-cluster` |
209262

210263
### EC2 Requirements
211264

@@ -266,13 +319,23 @@ mkdir -p ~/task-manager
266319
- Kubernetes service names are DNS — containers reach each other by service name, not IP
267320
- `kubectl describe pod` is more useful than `kubectl logs` when a pod won't start
268321

322+
**From adding Terraform + EKS:**
323+
- Terraform state is not optional in CI/CD — GitHub Actions runners are ephemeral, state must live in S3
324+
- You cannot downgrade Kubernetes versions — AWS only allows upgrades. Always pin to a stable tested version, not the latest
325+
- EKS node groups fail silently — `NodeCreationFailure` can mean anything from wrong subnet tags to instance type capacity issues to a known bug in a new K8s version
326+
- The bootstrap pattern exists for a reason — you can't use Terraform to create the bucket that stores Terraform's own state
327+
- `terraform plan` in CI on feature branches, `terraform apply` only on main — never apply blindly without reviewing the plan
328+
- Terraform and application deployments have different lifecycles — keep them in separate jobs with separate triggers
329+
269330
**Carried over from Jenkins version:**
270331
- Always use relative URLs in React — `localhost` in fetch calls breaks in production
271332
- `docker compose ps` showing `Up` is not the same as healthy — always add health checks
272333
- Env vars with duplicate keys in JS objects silently use the last value — never hardcode credentials
273334

274335
---
275336

337+
## Improvements
338+
276339
### Completed
277340
- [x] Migrated from Jenkins to GitHub Actions — zero CI server overhead
278341
- [x] Automated database table creation via `docker-entrypoint-initdb.d/`
@@ -284,17 +347,21 @@ mkdir -p ~/task-manager
284347
- [x] Secrets management via Kubernetes Secrets
285348
- [x] Persistent storage for database via PVC
286349
- [x] Automated API smoke tests after deployment
350+
- [x] Terraform provisions full AWS infrastructure — VPC, subnets, EKS cluster
351+
- [x] Remote Terraform state in S3 with DynamoDB locking
352+
- [x] Bootstrap pattern for state infrastructure
353+
- [x] Terraform only runs on infra changes — app deploys skip provisioning
287354

288355
### Up Next
289356
- [ ] Helm charts — package manifests with configurable values
290357
- [ ] Multiple environments — dev, staging, production namespaces
291358
- [ ] Horizontal Pod Autoscaler
292-
- [ ] Provision EKS cluster with Terraform
293359
- [ ] Add security scanning — Trivy, Snyk, Checkov
294360
- [ ] Set up Prometheus + Grafana monitoring
361+
- [ ] HTTPS with cert-manager and Let's Encrypt on EKS
295362

296363
---
297364

298-
## License
365+
## 📄 License
299366

300367
MIT

0 commit comments

Comments
 (0)