Skip to content

KithupaG/task-manager-github-actions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

13 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“‹ PERN Task Manager β€” GitHub Actions CI/CD Pipeline

CI/CD Docker AWS PostgreSQL React Node.js Nginx

A full-stack PERN (PostgreSQL, Express, React, Node.js) task manager app deployed to AWS EC2 via a fully automated CI/CD pipeline using GitHub Actions and Docker.

v2 of this project β€” previously deployed with Jenkins. Migrated to GitHub Actions to remove the need for a separate CI server. See task-manager-cicd-pipeline for the Jenkins version.


πŸ—οΈ Architecture

Developer β†’ GitHub Push β†’ GitHub Actions β†’ Docker Hub β†’ AWS EC2
                                ↓
                    Build & push Docker images
                    Pin exact build number tag in compose
                    SCP docker-compose.yaml to EC2
                    SSH into EC2 β†’ docker compose up

Infrastructure

Component Technology
Source Control GitHub
CI/CD GitHub Actions
Image Registry Docker Hub
Production Server AWS EC2 (Amazon Linux 2023)
Database PostgreSQL 16 (Docker)
Backend Node.js + Express
Frontend React + Nginx

πŸš€ Pipeline Stages

build-and-push ──────────────────────────► deploy
   β”œβ”€β”€ Checkout code                          β”œβ”€β”€ Checkout code
   β”œβ”€β”€ Log in to Docker Hub                   β”œβ”€β”€ Pin image tags in compose
   β”œβ”€β”€ Build & push client image              β”œβ”€β”€ SCP compose file to EC2
   └── Build & push server image              └── SSH β†’ docker compose up -d

The deploy job has needs: build-and-push β€” it only runs if the build succeeds. Both jobs run on fresh GitHub-hosted Ubuntu VMs.


πŸ“ Project Structure

task-manager-github-actions/
β”‚
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       └── deploy.yml              # Full pipeline definition
β”‚
β”œβ”€β”€ client/                         # React frontend
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”‚   β”œβ”€β”€ InputTodo.js        # Add todo β€” uses relative /api/todo
β”‚   β”‚   β”‚   β”œβ”€β”€ ListTodos.js        # List + delete β€” uses relative /api/todos
β”‚   β”‚   β”‚   └── EditTodo.js         # Edit modal β€” uses relative /api/todos/:id
β”‚   β”‚   └── App.js
β”‚   β”œβ”€β”€ nginx.conf                  # Proxies /api/* to backend container
β”‚   β”œβ”€β”€ Dockerfile                  # Multi-stage: node build β†’ nginx serve
β”‚   └── package.json
β”‚
β”œβ”€β”€ server/                         # Express backend
β”‚   β”œβ”€β”€ index.js                    # REST API routes β€” all prefixed /api
β”‚   β”œβ”€β”€ db.js                       # PostgreSQL connection with retry logic
β”‚   β”œβ”€β”€ database.sql                # Table init script
β”‚   β”œβ”€β”€ Dockerfile
β”‚   └── package.json
β”‚
β”œβ”€β”€ database.sql                    # Mounted into postgres on fresh deploy
β”œβ”€β”€ docker-compose.yaml             # Production compose β€” image tags pinned by pipeline
└── README.md

πŸ”§ Key Technical Decisions

GitHub Actions over Jenkins 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.

Nginx reverse proxy in the frontend container React fetch calls use relative URLs (/api/todos) with no hardcoded host or port. Nginx forwards all /api/* traffic to the backend container internally. The same Docker image works in any environment with zero config changes.

location /api {
    proxy_pass http://todo-backend:5000;
}

Build number pinning 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.

- name: Pin image tags in compose file
  run: |
    sed -i "s|image: .../server:.*|image: .../server:${{ github.run_number }}|" docker-compose.yaml
    sed -i "s|image: .../client:.*|image: .../client:${{ github.run_number }}|" docker-compose.yaml

Automatic database initialisation database.sql is SCP'd to EC2 alongside docker-compose.yaml and mounted into the postgres container via docker-entrypoint-initdb.d/. On a completely fresh deployment the table is created automatically β€” no manual steps.

Health checks on all services All three containers report real status. Backend and frontend are checked via HTTP, database via pg_isready. Dependent services wait for healthy status before starting β€” the backend won't attempt DB connections until postgres is confirmed ready.


πŸ”„ Comparison with Jenkins Version

Jenkins Version GitHub Actions Version
CI Server Jenkins on DigitalOcean droplet GitHub hosted runners
Server cost ~$6/month droplet Free
Pipeline file Jenkinsfile (Groovy) deploy.yml (YAML)
Credentials Jenkins credential store GitHub Secrets
Docker access Socket mount required Built into runner
SSH to EC2 Manual key setup in container appleboy/ssh-action
Image tagging BUILD_NUMBER github.run_number
Trigger GitLab webhook GitHub push event

πŸ› οΈ Local Development Setup

Prerequisites

  • Docker & Docker Compose
  • Node.js 20+

Run locally

# Clone the repo
git clone https://github.com/yourusername/task-manager-github-actions.git
cd task-manager-github-actions

# Create a .env file
cp .env.example .env
# Fill in your values

# Start everything
docker compose up --build

App available at http://localhost.

Environment Variables

DB_HOST=db
DB_USER=postgres
DB_PASSWORD=yourpassword
DB_NAME=todo_db

πŸ“‘ API Endpoints

Method Endpoint Description
GET /api/todos Get all todos
GET /api/todos/:id Get a single todo
POST /api/todo Create a new todo
PUT /api/todos/:id Update a todo
DELETE /api/todos/:id Delete a todo

βš™οΈ Replicating This Pipeline

GitHub Secrets Required

Go to Settings β†’ Secrets and variables β†’ Actions and add:

Secret Value
DOCKERHUB_USERNAME Your Docker Hub username
DOCKERHUB_TOKEN Docker Hub access token (not password)
EC2_HOST EC2 public IP or Elastic IP
EC2_USER ec2-user
EC2_SSH_KEY Private key contents (PEM format)

EC2 Requirements

# Install Docker
sudo yum update -y && sudo yum install -y docker
sudo systemctl start docker && sudo systemctl enable docker
sudo usermod -aG docker ec2-user

# Install Docker Compose plugin
sudo mkdir -p /usr/local/lib/docker/cli-plugins
sudo curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 \
  -o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose

# Create deploy directory
mkdir -p ~/task-manager

Security Group Inbound Rules

Port Protocol Source Purpose
22 TCP 0.0.0.0/0 GitHub Actions SSH
80 TCP 0.0.0.0/0 App access

πŸ’‘ Lessons Learned

From migrating Jenkins β†’ GitHub Actions:

  • Pipeline concepts are identical across tools β€” triggers, jobs, steps, secrets. Learning one makes the next trivial.
  • GitHub Actions prebuilt actions (docker/login-action, appleboy/ssh-action) replace shell scripting boilerplate. Less code, fewer bugs.
  • Not needing a CI server eliminates an entire category of infrastructure problems β€” no Docker socket mounts, no SSH key management inside containers, no server maintenance.
  • needs: in GitHub Actions is more explicit than Jenkins stage ordering β€” you declare job dependencies intentionally.
  • A running container might be from an old image β€” always verify the tag matches your latest build number before debugging.

Carried over from Jenkins version:

  • Always use relative URLs in React β€” localhost in fetch calls breaks in production
  • docker compose ps showing Up is not the same as healthy β€” always add health checks
  • Env vars with duplicate keys in JS objects silently use the last value β€” never hardcode credentials

πŸ“Œ Improvements

βœ… Completed

  • Migrated from Jenkins to GitHub Actions
  • Automated database table creation via docker-entrypoint-initdb.d/
  • Docker health checks on all services
  • Pinned image tags β€” exact build number deployed, never :latest

πŸ”œ Up Next

  • Provision EC2 infrastructure with Terraform
  • Add security scanning β€” Trivy, Snyk, Checkov
  • Add Prometheus + Grafana monitoring
  • Migrate to Kubernetes deployment

πŸ“„ License

MIT

About

Deploying this simple todo app to an EC2 Instance through Github Actions

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors