Guides
How to Deploy a Node.js App to EC2 with GitHub Actions
By Ravindra Bagale, DevOps trainer · LinkedIn · Published: · Last updated:
Build Node in GitHub Actions, copy to EC2, systemctl restart, curl /health. Keep the SSH key in CI secrets.
Friends, CI builds the zip, SSH only copies and restarts. Chala mitrano — never store the production private key in the repo.
मित्रांनो, CI zip बनवते, SSH फक्त copy आणि restart करते. Chala mitrano — production private key repo मध्ये ठेवू नका.
मित्रों, CI zip बनाती है, SSH सिर्फ copy और restart करता है। Chala mitrano — production private key repo में न रखें।
Quick answer
Hold this vertical picture:
- Add /health.
- Write deploy workflow.
- Copy artifact to EC2.
- Restart systemd.
- curl health; keep previous release.
Language: English · मराठी · हिंदी
Node.js to EC2 path
Why this path
Teams still run Node on one EC2 for small apps. CI must build the same way every push.
Why: Manual SCP drifts and breaks Friday deploys.
What: GitHub Actions builds, copies, restarts, curls.
How: Workflow + systemd + health check.
Example 1: Local Node health route
Type this:
mkdir -p /tmp/node-ec2 && printf '%s\n' 'const http=require("http");http.createServer((q,r)=>{if(q.url==="/health"){r.writeHead(200);r.end("ok")}else r.end("hi")}).listen(3000)' > /tmp/node-ec2/server.js && node -e "require('fs').accessSync('/tmp/node-ec2/server.js');console.log('server.js ready')"
You will see:
What it means: Tiny /health route — Flipkart-style health checks.
Example 2: Run and curl health
Type this:
cd /tmp/node-ec2 && node server.js & sleep 0.4; curl -s http://127.0.0.1:3000/health; kill %1 2>/dev/null || true
You will see:
What it means: Fix the app locally before you trust CI.
Example 3: Package a tarball
Type this:
cd /tmp/node-ec2 && tar -czf /tmp/app.tgz server.js && tar -tzf /tmp/app.tgz
You will see:
What it means: CI ships a small artifact list.
Concept 1: Build artifact in CI
npm ci then package only what production needs.
Why: Laptop builds hide missing lockfiles.
What: Clean install and a small tarball.
How: checkout → npm ci → tar.
Example 3: Package a tarball
Type this:
cd /tmp/node-ec2 && tar -czf /tmp/app.tgz server.js && tar -tzf /tmp/app.tgz
You will see:
What it means: CI ships a small artifact list.
Example 4: Workflow sketch
Type this:
mkdir -p /tmp/node-ec2/.github/workflows && printf '%s\n' 'name: deploy-ec2' 'on: [push]' 'jobs:' ' deploy:' ' runs-on: ubuntu-latest' ' steps:' ' - uses: actions/checkout@v4' ' - run: tar -czf app.tgz server.js' ' - run: echo SCP_TO_EC2_HERE' > /tmp/node-ec2/.github/workflows/deploy.yml && wc -l /tmp/node-ec2/.github/workflows/deploy.yml
You will see:
9 /tmp/node-ec2/.github/workflows/deploy.yml
What it means: Workflow under .github/workflows; key stays in CI secrets.
Example 10: Secrets stay out of git
Type this:
printf '%s\n' 'ok: store EC2_SSH_KEY only in CI secrets'
You will see:
ok: store EC2_SSH_KEY only in CI secrets
What it means: Amazon/Flipkart runbooks ban committing PEM files.
Concept 2: Copy and restart on EC2
SSH/rsync places files; systemd owns the process.
Why: Killing node by hand forgets reboot recovery.
What: Unit file + restart + is-active.
How: rsync → systemctl restart.
Example 5: systemd unit sketch
Type this:
printf '%s\n' '[Unit]' 'Description=demo node' '[Service]' 'ExecStart=/usr/bin/node /opt/app/server.js' 'Restart=always' '[Install]' 'WantedBy=multi-user.target' > /tmp/node-app.service && head -3 /tmp/node-app.service
You will see:
What it means: TCS/Capgemini expect systemd so reboot recovers.
Example 6: SSH config pattern
Type this:
printf '%s\n' 'Host app-ec2' ' HostName 203.0.113.10' ' User ec2-user' ' IdentityFile ~/.ssh/app-ec2.pem' > /tmp/ssh-demo && cat /tmp/ssh-demo
You will see:
What it means: Dedicated deploy key — never paste PEM into YAML.
Example 7: rsync dry-run style
Type this:
mkdir -p /tmp/ec2-opt-app && rsync -avn /tmp/node-ec2/server.js /tmp/ec2-opt-app/ && ls /tmp/ec2-opt-app
You will see:
What it means: Drop -n on the real host after you trust the list.
Example 8: Remote restart commands
Type this:
printf '%s\n' 'sudo systemctl restart node-app && sudo systemctl is-active node-app' > /tmp/remote-restart.sh && chmod 750 /tmp/remote-restart.sh && cat /tmp/remote-restart.sh
You will see:
sudo systemctl restart node-app && sudo systemctl is-active node-app
What it means: is-active must print active.
Concept 3: Prove and roll back
Health curl in CI; keep previous release.
Why: Green pipeline with a dead app fools the team.
What: curl -fsS and a previous folder.
How: health → previous symlink.
Example 9: Health in the job
Type this:
printf '%s\n' 'curl -fsS http://127.0.0.1:3000/health' > /tmp/healthcheck.sh && cat /tmp/healthcheck.sh
You will see:
curl -fsS http://127.0.0.1:3000/health
What it means: -f fails the job on HTTP errors.
Example 11: Rollback folders
Type this:
mkdir -p /tmp/releases && printf 'v1\n' > /tmp/releases/previous && printf 'v2\n' > /tmp/releases/current && cat /tmp/releases/previous /tmp/releases/current
You will see:
What it means: Keep previous release for a fast flip.
Example 12: journalctl hint
Type this:
printf '%s\n' 'journalctl -u node-app -n 50 --no-pager'
You will see:
journalctl -u node-app -n 50 --no-pager
What it means: When health fails, read the unit log first.
Deploy card
1 Build in CI 2 Copy to EC2 3 systemctl restart 4 curl /health 5 Keep previous release
Suppose: Permission denied on SCP
Deploy user needs write on /opt/app and sudo for systemctl.
Suppose: Health fails after restart
journalctl -u node-app -n 50; check PORT and NODE_ENV.
Ravindra Bagale's Tip
At Infosys and Accenture, Node on EC2 usually runs under systemd. Reviewers ask for a health curl after every deploy.
Infosys आणि Accenture मध्ये Node EC2 वर systemd खाली चालते. Reviewers प्रत्येक deploy नंतर health curl विचारतात.
Infosys और Accenture में Node EC2 पर systemd के नीचे चलता है। Reviewers हर deploy के बाद health curl पूछते हैं।
Check yourself (quiz)
Q1. Where is the workflow file?
.github/workflows
Q2. Who owns the process?
systemd unit
Q3. Prove deploy?
curl /health with -f
Q4. Where is the SSH key?
CI secrets, never the repo
Q5. Rollback?
Previous release folder
Q6. Why npm ci?
Lockfile-faithful install
Reading tracking
Mark this guide when you finish. Your browser remembers it on this device.
push → build → copy → restart → curl.
Did you understand? Let us try it once more.
Frequently asked questions
Why systemd?
Reboot-safe process manager on EC2.
Why not store the key in YAML?
Repo access would steal production.
OIDC option?
Prefer short-lived roles when the path allows.
What if health fails?
Read journalctl for the unit.
rsync or SCP?
Both work; rsync is nicer for many files.
Rollback?
Point back to the previous folder.