DEV Community

Cover image for From Local Node.js App to Automated AWS Deployment: A Full CI/CD Pipeline with GitHub Actions
Kolarinde Awopetutimileyin
Kolarinde Awopetutimileyin

Posted on

From Local Node.js App to Automated AWS Deployment: A Full CI/CD Pipeline with GitHub Actions

Project Overview

This project documents a complete continuous deployment pipeline: a Node.js application built locally, pushed to GitHub, deployed onto a provisioned AWS EC2 instance, and finally automated so that every future git push triggers a hands-free deployment via GitHub Actions and SSH. It touches nearly every layer of a real DevOps workflow — app scaffolding, source control, cloud infrastructure, security groups, secrets management, and pipeline-as-code.

Skills demonstrated: Node.js/Express project scaffolding, Git version control, GitHub repository management, AWS EC2 provisioning, security group configuration, SSH key-based server access, GitHub Actions workflow authoring (YAML), GitHub Secrets management, automated remote deployment scripting, process management with PM2.

Screen-by-Screen Breakdown
Screen 1 — Scaffolding the Node.js Project

Initialized a new Node.js project from the command line, generating a package.json to track dependencies and scripts for the application.

Steps:

Create and enter the project directory: mkdir aws-cicd-lab && cd aws-cicd-lab.
Run npm init -y to generate a default package.json.
Confirm the file was created with the expected project metadata.
Screen 2 — Installing Express and Defining the Start Script


Added a start script to package.json and installed Express as the project's web framework, preparing the app to serve HTTP responses.

Steps:

Edit package.json to add "start": "node app.js" under scripts.
Run npm install express.
Confirm Express appears under dependencies in package.json.

Screen 3 — Verifying the App Locally

Ran the application locally and confirmed it served the expected response in the browser before pushing any code — catching issues early, before they reach the cloud.


Steps:

Run npm start (or node app.js) locally.
Open localhost:3000 in the browser.
Confirm the expected response renders ("CI/CD Deployment Successful!").

Screen 4 — Creating the GitHub Repository


Created a new public GitHub repository (node-app-actions) to host the project's source code and serve as the trigger point for the deployment pipeline.

Steps:

On GitHub, click New repository.
Set the Repository name (node-app-actions) and visibility (Public).
Leave README/.gitignore/license unchecked (added manually).
Click Create repository.

Screen 5 — Reviewing the Push Instructions


Reviewed GitHub's auto-generated command-line instructions for connecting a local repository to the newly created remote.

Steps:

Note the "push an existing repository from the command line" commands.
Copy the git remote add origin URL for use in the local terminal.

Screen 6 — Pushing the Local Repository to GitHub

Connected the local Git repository to the GitHub remote and pushed the initial commit, making the codebase available as the deployment source.


Steps:

Run git remote add origin https://github.com//node-app-actions.git.
Run git branch -M main.
Run git push -u origin main.
Confirm the push output shows objects written and the branch tracking origin/main.

⚠️ Security note: This repo push included vpc-key.pem — the EC2 private key — as a tracked file in a public repository. A private key committed to a public repo is a live credential exposure, not just a portfolio detail. Before publishing this project: rotate/replace the EC2 key pair in AWS, remove the file from Git history (git filter-repo or BFG Repo-Cleaner — deleting it in a new commit isn't enough, history still has it), and add *.pem to .gitignore going forward. The project later handles the SSH key correctly via GitHub Secrets (Screens 15–16) — the same discipline should apply here too.

Screen 7 — Launching the EC2 Instance

Began provisioning a new AWS EC2 instance (nodeApp) using a free-tier-eligible Ubuntu 24.04 LTS image as the deployment target.


Steps:

In the AWS Console, go to EC2 → Launch an instance.
Set the Name (nodeApp).
Under Quick Start, select Ubuntu → Ubuntu Server 24.04 LTS.
Confirm the instance summary (AMI, instance type) before continuing.

Screen 8 — Configuring Network Settings and Security Group

Configured the instance's network settings, creating a new security group (node-app-nsg) with explicit inbound rules for SSH (port 22) and HTTP (port 80).


Steps:

Under Network settings, confirm the VPC and subnet.
Choose Create security group and name it (node-app-nsg).
Add an inbound rule: Type SSH, Port 22, Source Anywhere (or restrict to your IP for better security).
Add a second inbound rule: Type HTTP, Port 80.
Click Launch instance.

Screen 9 — Confirming the Instance Is Running

Verified the new EC2 instance launched successfully, noting its public IP, instance type, and associated VPC/subnet details.

Steps:

Open EC2 → Instances and select the new instance.
Confirm Instance state shows Running.
Note the Public IPv4 address and Instance type (t3.micro) for later use.

Screen 10 — Retrieving the SSH Connect Command

Used the EC2 console's built-in Connect panel to generate the correct SSH command for accessing the instance, referencing the downloaded private key.


Steps:

Select the instance and click Connect.
Open the SSH client tab.
Note the chmod 400 command to secure the key file's permissions.
Copy the generated SSH command (ssh -i ".pem" ubuntu@).

Screen 11 — Connecting to the Instance via SSH

Established an SSH session into the EC2 instance from the local terminal, confirming a clean Ubuntu boot with no pending critical issues.


Steps:

In the local terminal, run the copied SSH command with the correct key path.
Confirm the Ubuntu welcome banner and system status appear.
Note the instance's internal IP for reference.

Screen 12 — Preparing the Server (Nginx & SSH Keys)

Enabled and started Nginx on the instance as a base web-facing service, then inspected the server's SSH key configuration to confirm what was authorized for access.


Steps:

Run sudo systemctl enable nginx.
Run sudo systemctl start nginx.
Inspect ~/.ssh/authorized_keys to confirm the expected public key is present.

Screen 13 — Navigating to Repository Settings

Opened the GitHub repository's Settings tab to begin configuring the secrets that GitHub Actions would need to deploy to the EC2 instance securely.

Steps:

Open the repository on GitHub.
Click the Settings tab.
Locate Secrets and variables in the left sidebar.

Screen 14 — Opening Actions Secrets

Navigated into the Actions secrets section, where sensitive deployment values (host, username, private key) would be stored securely rather than hardcoded into the workflow file.


Steps:

Under Secrets and variables, click Actions.
Confirm the Actions secrets panel is ready to accept new entries.

Screen 15 — Adding the VM_HOST Secret

Stored the EC2 instance's public IP as a GitHub Actions secret (VM_HOST), so the deployment workflow could reference it without exposing it in the codebase.


Steps:

Click New repository secret.
Set Name to VM_HOST.
Set Secret to the EC2 public IP address.
Click Add secret.

Screen 16 — Preparing the SSH Private Key for a Secret

Opened the EC2 private key file to copy its full contents, in preparation for storing it as a GitHub Actions secret (VM_SSH_KEY) rather than committing it to the repo.


Steps:

Open the .pem key file in a text editor.
Select and copy the entire contents, including the BEGIN/END RSA PRIVATE KEY lines.
Paste the full key into a new GitHub secret named VM_SSH_KEY.

This is the correct way to handle the private key — as a GitHub Secret, never as a tracked file. It's worth applying the same standard retroactively to Screen 6.

Screen 17 — Scaffolding the GitHub Actions Workflow

Created the folder structure GitHub Actions requires (.github/workflows/) and an empty workflow file to hold the deployment pipeline definition.


Steps:

From the project root, run mkdir .github.
Run mkdir workflows inside it, then cd workflows.
Run touch deploy.yml to create the workflow file.

Screen 18 — Writing the Deployment Job

Authored the core deployment step using the appleboy/ssh-action, referencing the GitHub Secrets to SSH into the EC2 instance and run a deploy script — cloning on first run, pulling and restarting via PM2 on subsequent runs.


Steps:

Define a deploy job under jobs:.
Add a step using uses: appleboy/ssh-action@v0.1.10.
Reference secrets.VM_HOST, secrets.VM_USER, and secrets.VM_SSH_KEY under with:.
Write the deploy script: — conditionally clone the repo, then git pull, npm install, and restart via pm2 restart app || pm2 start app.js --name app.
Screen 19 — Adding the Checkout Step and Triggering the Pipeline

Completed the workflow with a trigger (on: push) and a checkout step, then committed and pushed the workflow file — which immediately kicked off the first automated deployment.


Steps:

Add on: push: at the top of the workflow to trigger on every push to the branch.
Add a Checkout Code step using actions/checkout@v3.
Run git add .github/, git commit -m "added aws workflow file", and git push origin main.
Watch the Actions tab on GitHub to confirm the workflow runs and deploys successfully.

Top comments (0)