You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
13
14
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.
15
16
16
17
---
17
18
@@ -22,8 +23,10 @@ A full-stack PERN (PostgreSQL, Express, React, Node.js) task manager app with tw
22
23
| Source Control | GitHub |
23
24
| CI/CD | GitHub Actions |
24
25
| Image Registry | Docker Hub |
25
-
| Production Server | AWS EC2 (Amazon Linux 2023) |
└── 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
+
56
73
The `deploy` job has `needs: build-and-push` — it only runs if the build succeeds. Both jobs run on fresh GitHub-hosted Ubuntu VMs.
57
74
58
75
---
@@ -93,6 +110,14 @@ task-manager-github-actions/
93
110
│ └── frontend/
94
111
│ └── deployment.yaml # Frontend Deployment + NodePort Service
│ ├── 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
+
│
96
121
├── database.sql # Mounted into postgres on fresh deploy
97
122
├── docker-compose.yaml # Local dev + EC2 compose deployment
98
123
└── README.md
@@ -114,6 +139,30 @@ location /api {
114
139
}
115
140
```
116
141
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
+
117
166
**Build number pinning**
118
167
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.
119
168
@@ -201,11 +250,15 @@ Go to **Settings → Secrets and variables → Actions** and add:
201
250
202
251
| Secret | Value |
203
252
|--------|-------|
204
-
| `DOCKERHUB_USERNAME` | Your Docker Hub username |
| `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` |
209
262
210
263
### EC2 Requirements
211
264
@@ -266,13 +319,23 @@ mkdir -p ~/task-manager
266
319
- Kubernetes service names are DNS — containers reach each other by service name, not IP
267
320
- `kubectl describe pod`is more useful than `kubectl logs` when a pod won't start
268
321
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
+
269
330
**Carried over from Jenkins version:**
270
331
- Always use relative URLs in React — `localhost` in fetch calls breaks in production
271
332
- `docker compose ps`showing `Up` is not the same as healthy — always add health checks
272
333
- Env vars with duplicate keys in JS objects silently use the last value — never hardcode credentials
273
334
274
335
---
275
336
337
+
## Improvements
338
+
276
339
### Completed
277
340
- [x] Migrated from Jenkins to GitHub Actions — zero CI server overhead
278
341
- [x] Automated database table creation via `docker-entrypoint-initdb.d/`
0 commit comments