A production-tested, self-hosted CI/CD orchestrator built with Node.js.
It receives signed GitHub push events, validates deployment policy, builds the exact commit in an isolated workspace, deploys an immutable release, verifies application health, sends email status notifications, and can roll back to a known-good version.
This is an educational and portfolio project for understanding CI/CD internals. It is not intended to replace distributed platforms such as GitHub Actions, Jenkins, or GitLab CI.
GitHub push
-> verify raw-body HMAC signature
-> validate event, repository, and branch
-> create persistent idempotent job
-> enter per-project queue
-> checkout exact commit SHA
-> install -> test -> build
-> deploy through selected adapter
-> retry public health check
-> record healthy release or roll back
-> send success/failure email
- Correct GitHub
X-Hub-Signature-256verification with constant-time comparison - Repository and branch allowlisting
- Persistent jobs and timestamped pipeline logs
- Atomic duplicate-delivery protection
- One-at-a-time deployment queue per project
- Isolated checkout of the exact pushed commit
- Shell-free command execution with timeouts and secret redaction
- Local, SSH/EC2, S3-static, and generic deploy-hook adapters
- Retrying health checks and known-good release tracking
- Automatic rollback orchestration and rollback health verification
- SMTP success and failure notifications
- Production assets for Nginx, systemd, PM2, and versioned EC2 releases
- 33 automated tests
GitHub
|
| HTTPS webhook
v
Nginx -> Express routes -> security middleware -> controller
|
v
pipeline services
/ | \
repositories queue deploy adapters
|
v
target host / provider
The code follows a route → middleware → controller → service → repository structure. Provider-specific behavior stays behind deployment adapters.
- Node.js 22+
- npm
- Git
git clone https://github.com/Kevin1skyrj/Custom_CI_CD_Server.git
cd Custom_CI_CD_Server
npm ci
cp .env.example .envGenerate a webhook secret:
openssl rand -hex 32Set at least these values in .env:
GITHUB_WEBHOOK_SECRET=your-random-secret
ALLOWED_REPOSITORY=owner/repository
ALLOWED_BRANCH=main
REPOSITORY_CLONE_URL=https://github.com/owner/repository.gitThe included stage configuration models DataDock's server/ and client/ directories. Adapt src/config/pipeline.config.js to the trusted commands and directory layout of your application.
Choose and configure one deployment adapter. For a safe local experiment:
DEPLOYMENT_TYPE=local
LOCAL_DEPLOY_DIR=./data/local-releases
PIPELINE_DATA_DIR=./data/pipeline-jobs
PIPELINE_WORKSPACE_DIR=./data/workspacesRun the server:
npm run devRun the tests:
npm testIn the repository, open Settings → Webhooks → Add webhook and configure:
Payload URL: https://your-ci-domain.example/webhook/github
Content type: application/json
Secret: same value as GITHUB_WEBHOOK_SECRET
Events: push only
SSL: enabled
A new delivery returns HTTP 202; a duplicate delivery returns HTTP 200 with the original job ID.
POST /webhook/github
GET /pipeline-jobs/:jobIdThe included Nginx configuration exposes only the exact webhook path. Keep the job-details endpoint private until authentication is added.
| Adapter | Use case | Activation model |
|---|---|---|
local |
Learning and same-host deployments | Immutable local directory plus activation manifest |
ssh |
Linux servers and AWS EC2 | scp, trusted remote script, atomic release symlink |
s3-static |
Static sites | Versioned S3 prefix and current.json manifest |
deploy-hook |
Vercel, Netlify, Render, or custom providers | Secret provider hook; acceptance is not reported as completion |
Select the adapter with:
DEPLOYMENT_TYPE=local|ssh|s3-static|deploy-hookThe full configuration reference is documented in .env.example.
GitHub
-> HTTPS webhook on dedicated Ubuntu CI host
-> exact-SHA test and build
-> private VPC SSH
-> /var/www/datadock-deploy/releases/<job-id>
-> atomic current symlink
-> PM2 reload
-> Nginx
-> public API health check
-> email result
The production workflow activated two healthy releases, delivered failure and success emails, rolled back to the previous release, verified API/frontend health, and reactivated the latest release.
Deployment assets are in deploy/. The human-reviewed production procedure and evidence are in Milestone 10 notes.
- Verify the signature over untouched request bytes before JSON parsing.
- Never trust commands, hosts, paths, branches, or repositories from webhook payloads.
- Validate job IDs and commit SHAs before using them in paths or commands.
- Spawn commands without a shell and pass arguments separately.
- Use dedicated deployment credentials and strict SSH host-key checking.
- Keep build/runtime secrets outside Git workspaces and versioned releases.
- Expose only the webhook route publicly.
- Record only health-verified releases as rollback targets.
- Persist pipeline outcome before sending best-effort notifications.
Never commit .env, private keys, SMTP credentials, AWS credentials, or production application environment files.
npm testThe 33-test suite covers webhook authentication, tamper detection, validation, persistence, duplicate deliveries, queue ordering, exact commit checkout, stage failures, deployment adapters, health retries, notifications, and rollback behavior.
- One Node.js process with filesystem persistence
- In-memory queue is not reconstructed after restart
- No dashboard or authentication for the job-details API
- No distributed workers or locks
- No automatic workspace, log, or release retention policy
- Deploy-hook adapters do not poll managed providers to terminal status
- S3 activation requires a consumer for the
current.jsonmanifest
These boundaries are deliberate: the project focuses on the core CI/CD lifecycle while keeping the implementation explainable.
Built by Rajat Pandey as a hands-on study of secure CI/CD design and production deployment.