DEV Community

Cover image for 🚀 CI/CD for Terraform with GitHub Actions: Deploying a Node.js + Redis App on AWS

🚀 CI/CD for Terraform with GitHub Actions: Deploying a Node.js + Redis App on AWS

A hands-on guide to automating infrastructure provisioning and containerised application deployment with GitHub Actions and Terraform.

💡 Introduction

Welcome to the world of pipelines and automation 🚀. In this guide, we’re going to uncover an exciting project where we deploy a Node.js + Redis web app on AWS using Terraform and GitHub Actions for seamless integration.

This walkthrough is designed with beginners in mind—so even if you’re just starting with DevOps or cloud automation, you’ll be able to follow along step by step.

The application itself is simple but practical: a Request Counter app that tracks the number of visits and stores the data in Redis. But the real magic isn’t the app—it’s the way we’ll set up Infrastructure as Code (IaC) with Terraform, integrate it with GitHub Actions CI/CD, and see how everything ties together.

So without further ado, let’s roll up our sleeves and get hands-on with some practical knowledge 💡.


🛠 Pre-requisites

Before diving into the project, let’s make sure our host system is ready with all the essentials. Here’s what you’ll need:

  • AWS Account + IAM User with Admin Access (Only for testing purposes, in prod, always use PLOP). Since we’ll be spinning up infrastructure on AWS, you need an IAM user with proper permissions and AWS CLI configured locally.
    👉 If you’re new to this, don’t worry—I’ve explained the entire setup process (IAM user, AWS CLI, and Terraform installation) in one of my earlier blogs: Learn How to Deploy a Three-Tier Application on AWS EKS Using Terraform with Best Practices

  • Docker & Docker Compose: Our application runs in containers, so Docker and Docker Compose are a must. You can install them easily from their official documentation: Install Docker

✅ Once these are configured, we’re all set to start building and automating this project!


🚀 Getting Started with the Project

The complete code for this project is available on my GitHub repository:
👉 nginx-node-redis

To begin, fork this repository under your own GitHub username and then clone it locally using the commands below:

git clone https://github.com/<your-username>/nginx-node-redis.git
cd nginx-node-redis/
Enter fullscreen mode Exit fullscreen mode

Why this repo?

The project originally comes from Docker’s official dockersamples collection. It’s a simple Node.js + Redis request counter application with an NGINX load balancer in front. Every time you refresh the page, the counter increments and you also see the hostname (web1 or web2) that served your request.

I’ve taken that base project and leveled it up to make it more attractive and production-friendly.

🔑 Key Highlights of the Project

  • web/server.js → Basic Node.js app that connects to Redis on port 6379 and returns:

    • The number of visits (increments with every refresh)
    • The hostname (web1 or web2) serving the request
    • Runs on port 5000
    • Now enhanced with a beautiful HTML UI instead of plain text 🎨
  • nginx/nginx.conf → Custom configuration with:

    • An upstream load balancer pointing to web1:5000 and web2:5000
    • Proxy pass rules to evenly distribute traffic
    • Dockerfile that replaces the default NGINX config with our custom one
  • docker-compose.yml → Glues everything together:

    • Redis container (database)
    • Two Node.js containers (web1 and web2)
    • NGINX container (acting as a reverse proxy and load balancer)

✅ My Enhancements

  • ✨ Improved UI in web/server.js

  • 📦 Added Terraform configuration (terra-config/) to deploy the app on AWS

  • ⚡ Added GitHub Actions workflow (.github/workflows/main.yml) for CI/CD automation

With these changes, the project is no longer just a demo—it’s a mini production-grade system that you can deploy with one push.


🐳 Testing Locally with Docker Compose

Before deploying this app on AWS with Terraform, let’s test it locally to make sure everything works as expected.

Navigate to the project root directory and run the following command:

docker-compose up --build
Enter fullscreen mode Exit fullscreen mode

✅ This will:

  • Build Docker images for Redis, Node.js web servers, and NGINX

  • Start up all the containers in the correct order

  • Expose the application through NGINX on port 80

If everything is set up properly, you’ll see logs confirming the containers are running. Most importantly, look for this message in your terminal:

The web server is listening on Port 5000.
Enter fullscreen mode Exit fullscreen mode

Now, open your browser and go to 👉 http://localhost:80

🎉 You should see the application live!

  • Every time you refresh the page, the counter will increment by +1

  • The hostname will change between web1 and web2, showing that load balancing is working perfectly

This step confirms that the Docker + NGINX + Redis setup is solid before we move on to cloud deployment.


⚡ Integrating GitHub Actions and Terraform

Now that our app works locally, let’s automate deployment with GitHub Actions + Terraform. This will allow us to:

  • Provision infrastructure on AWS automatically

  • Deploy the app onto an EC2 instance

  • Run health checks

  • Tear down resources after testing (to save 💰 on AWS bills)

🔑 Step 1: Add AWS Secrets

  • Go to your GitHub Dashboard → open the nginx-node-redis repo

  • Navigate to Settings > Secrets and Variables > Actions

  • Add the following Repository Secrets (from the IAM user you created earlier):

    • AWS_ACCESS_KEY_ID
    • AWS_SECRET_ACCESS_KEY

🏗 Step 2: Terraform Configuration

Inside the terra-config/main.tf file:

  • Uses the default VPC

  • Fetches the latest Ubuntu AMI

  • Creates a Security Group with port 22 (SSH) and port 80 (HTTP) open

  • Provisions a t2.micro EC2 instance using that SG and AMI

  • Includes a user-data script that installs Docker, Docker Compose, and runs our app

📌 In short: Terraform handles all infra creation and boots up an EC2 instance that runs our Request Counter app.

⚠️ Important Change:

In the user_data section of main.tf, update the GitHub repo URL to point to your forked repo:

git clone https://github.com/<your-github-username>/nginx-node-redis.git
Enter fullscreen mode Exit fullscreen mode

⚙️ Step 3: GitHub Actions Workflow

The .github/workflows/main.yml pipeline is triggered on push or pull requests. Here’s what happens step by step:

  1. Checkout the repository

  2. ⚙️ Setup Terraform

  3. 📂 Run terraform init, terraform validate, terraform plan

  4. 🚀 Apply infrastructure with terraform apply

  5. Wait 90 seconds → allows user-data to finish setting up Docker + app

  6. 🌍 Fetch the Public IP of the EC2 instance

  7. 🔎 Health check → ensure the app is live

  8. ⏱ Keep the application running for 5 minutes

  9. 🧹 Auto teardown with terraform destroy → ensures no unused AWS resources are left behind

▶️ Step 4: Trigger the Workflow

Once you’ve updated your repo URL in main.tf, commit and push changes:

git status
git add terra-config/main.tf
git commit -m "Updated GitHub repo URL"
git push origin main
Enter fullscreen mode Exit fullscreen mode

This push will trigger the GitHub Actions workflow, automatically deploying your app to AWS.


🚀 Testing the Deployment on AWS

Once everything is set up, it’s time to watch the magic happen ✨.

  • Go to your GitHub Repository Dashboard

  • Click on Actions

  • Find your recent commit – for example:

    "Updated GitHub repo URL"
    
  • Open the workflow run → you’ll see the terraform job executing step by step.

🟢 Wait until the workflow reaches the “Keep App Running Stage”. At this point, Terraform has already created your EC2 instance, installed Docker & Docker Compose, and started the application.

👉 Click on the Public URL shown in the logs, and… Viola! 🎉

Your Request Counter App is now live on an AWS EC2 instance 🚀.

  • You can interact with the app for 5 minutes

  • Every refresh increments the counter

  • Requests are distributed between web1 and web2 via the Nginx load balancer

⏳ After 5 minutes, GitHub Actions will automatically run terraform destroy to clean up resources and avoid unnecessary AWS charges 💸.


🔄 Real-Life Scenario: Updating Your App on the Fly

Now that our app is live, let’s simulate a real production change.

Imagine your manager says:

"Hey, can you update the heading from Welcome to Request Counter to Welcome to My Amazing Request Counter?"

Here’s how simple it becomes with GitHub Actions + Terraform:

  • Open the web/server.js file in your project

  • Find the <h1> HTML tag and update it:

    <h1>Welcome to My Amazing Request Counter</h1>
    

  • Push the changes to your repository:

    git status
    git add web/server.js
    git commit -m "Heading Changed"
    git push origin main
    

🎉 That’s it! GitHub Actions will pick up the changes, trigger the workflow, and within 5 minutes your updated heading will be live on the EC2 instance.

⚡ This is the true power of CI/CD pipelines — no manual SSH into servers, no redeploying by hand. Just push your code and let Terraform + GitHub Actions handle the rest 🚀.


🏁 Conclusion

In this blog, we took a simple Node.js + Redis counter application and supercharged it with Nginx, Docker, Terraform, and GitHub Actions.

What started as a local demo quickly transformed into a cloud-ready, automated CI/CD pipeline:

  • 🚀 Docker + Docker Compose handled local testing and containerization

  • Terraform provisioned AWS infrastructure seamlessly

  • 🔄 GitHub Actions automated deployments and teardown, giving us a clean, cost-effective workflow

  • 🎯 And most importantly — we saw how easy it is to make real-time changes that go live with just a git push.

This project proves how infrastructure as code + automation can save developers hours of repetitive work and make production-ready workflows more reliable.

Thanks for following along — I hope this inspires you to build your own Terraform + GitHub Actions pipelines!

📬 Let’s Connect:

Top comments (0)