Deploying a Node.js App on AWS ECS Fargate with Terraform and GitHub Actions
Introduction
Modern cloud-native applications benefit from automated infrastructure provisioning and deployment pipelines. By combining Terraform for Infrastructure as Code (IaC), Amazon ECS Fargate for serverless container orchestration, and GitHub Actions for Continuous Deployment (CD), you can build a reliable and repeatable deployment workflow.
In this guide, you'll deploy a simple Node.js "Hello World" application on Amazon ECS Fargate using Terraform to provision AWS resources. You'll also configure a GitHub Actions workflow that automatically builds and deploys the application whenever changes are pushed to the repository.
This approach enables consistent deployments, reduces manual effort, and improves the overall software delivery process.
Prerequisites
Before getting started, ensure you have the following:
| Requirement | Description |
|---|---|
| AWS Account | An AWS account with permissions to create and manage ECS, IAM, VPC, ECR, and related AWS resources. |
| GitHub Account | A GitHub account for hosting the application source code and configuring GitHub Actions workflows. |
| Terraform | Installed locally to provision AWS infrastructure using Infrastructure as Code (IaC). |
| Docker | Installed locally for building and testing container images. |
| Node.js | Basic familiarity with Node.js application development, Docker, and Terraform concepts. |
Important: Include only the overall architecture diagram showing the deployment workflow (GitHub โ GitHub Actions โ Amazon ECR โ Amazon ECS Fargate). This provides readers with a clear overview of the complete solution.
Understanding Amazon ECS Fargate
What Is AWS Fargate?
Amazon ECS Fargate is a serverless compute engine for running containerized applications on Amazon Elastic Container Service (Amazon ECS) and Amazon Elastic Kubernetes Service (Amazon EKS).
Unlike traditional container deployments, Fargate eliminates the need to provision or manage EC2 instances. AWS automatically handles the underlying infrastructure, allowing you to focus entirely on your application.
Key Benefits
- No server management
- Automatic infrastructure provisioning
- Built-in scalability
- Pay only for the CPU and memory resources your containers use
- Native integration with AWS services such as Amazon ECS, Amazon ECR, IAM, CloudWatch, and Application Load Balancer
Fargate is well suited for microservices, REST APIs, web applications, and event-driven workloads.
Understanding Terraform
What Is Terraform?
Terraform is an open-source Infrastructure as Code (IaC) tool developed by HashiCorp.
It enables developers and DevOps engineers to define cloud infrastructure using declarative configuration files rather than manually provisioning resources through a cloud console.
Terraform supports multiple cloud providers, including:
- Amazon Web Services (AWS)
- Microsoft Azure
- Google Cloud Platform (GCP)
- Oracle Cloud Infrastructure (OCI)
- VMware
- Kubernetes
Key Benefits
- Infrastructure as Code (IaC)
- Automated infrastructure provisioning
- Version-controlled infrastructure
- Repeatable deployments
- Multi-cloud support
- Consistent deployment workflows
Using Terraform ensures that infrastructure remains reproducible, scalable, and easy to maintain throughout the application lifecycle.
Step 1: Create a Simple Node.js Application
In this step, you'll create a basic Node.js web application using Express, a lightweight and flexible web framework for building server-side applications.
The application simply responds with "Hello World!", serving as the workload that will later be containerized and deployed to Amazon ECS Fargate.
Project Structure
Your project should have the following structure:
hello-world-app/
โโโ app.js
โโโ Dockerfile
โโโ package.json
โโโ .gitignore
Important: Include only one screenshot showing the project directory structure. The remaining screenshots can be omitted because the source code below is sufficient.
Create the Application
app.js
This file contains the application's main logic.
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Explanation
This application performs the following tasks:
- Imports the Express framework.
- Creates an Express application instance.
- Defines a single HTTP GET endpoint (
/). - Returns Hello World! when accessed.
- Starts the web server on the port defined by the
PORTenvironment variable or defaults to 3000.
Configure package.json
The package.json file defines the application's metadata, dependencies, and startup commands.
{
"name": "hello-world-app",
"version": "1.0.0",
"description": "Hello World Node.js app",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^4.17.1"
}
}
Explanation
The file includes:
- Application name and version.
- Project description.
- Entry point (
app.js). - Startup command.
- Required project dependencies.
In this example, Express is the only dependency.
Create the Dockerfile
To deploy the application on Amazon ECS Fargate, package it as a Docker container.
Create a Dockerfile.
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Explanation
This Dockerfile performs the following steps:
- Uses the official Node.js 14 image.
- Creates the application working directory.
- Copies the package files.
- Installs project dependencies.
- Copies the application source code.
- Exposes port 3000.
- Starts the application using
npm start.
At this point, the Node.js application is fully containerized and ready for deployment.
Step 2: Configure Terraform for Amazon ECS Fargate
Next, provision the AWS infrastructure using Terraform.
Terraform creates all of the resources required to deploy the application on Amazon ECS Fargate, including:
- ECS Cluster
- Task Definition
- ECS Service
- IAM Role
- Application Load Balancer
- Target Group
- Listener
Important: Include only one architecture diagram illustrating the Terraform-managed AWS infrastructure. Individual Terraform editor screenshots are unnecessary because the configuration files below are self-explanatory.
variables.tf
Define the variables used throughout the Terraform configuration.
variable "region" {
description = "The AWS region to create resources in"
type = string
default = "ap-south-1"
}
variable "ecs_cluster_name" {
description = "The name of the ECS cluster"
type = string
}
variable "app_name" {
description = "The name of the application"
type = string
}
variable "vpc_id" {
description = "The ID of the VPC where resources will be created"
type = string
}
variable "subnet_ids" {
description = "A list of subnet IDs for the ECS service"
type = list(string)
}
These variables make the Terraform configuration reusable across multiple environments.
main.tf
The main.tf file provisions the complete AWS infrastructure required for the application.
It creates:
- Amazon ECS Cluster
- IAM Execution Role
- ECS Task Definition
- ECS Service
- Application Load Balancer
- Target Group
- Listener
terraform {
backend "s3" {
bucket = "pavan-your-terraform-state-bucket"
key = "ecs/hello-world-app/terraform.tfstate"
region = "ap-south-1"
}
}
provider "aws" {
region = var.region
}
resource "aws_ecs_cluster" "cluster" {
name = var.ecs_cluster_name
}
resource "aws_iam_role" "ecs_task_execution_role" {
name = "ecsTaskExecutionRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})
managed_policy_arns = [
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
]
}
resource "aws_ecs_task_definition" "task" {
family = var.app_name
network_mode = "awsvpc"
cpu = "256"
memory = "512"
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
container_definitions = jsonencode([
{
name = var.app_name
image = "567738737859.dkr.ecr.ap-south-1.amazonaws.com/hello-world-app:latest"
essential = true
portMappings = [{
containerPort = 3000
hostPort = 3000
}]
}
])
}
resource "aws_ecs_service" "service" {
name = var.app_name
cluster = aws_ecs_cluster.cluster.id
task_definition = aws_ecs_task_definition.task.arn
desired_count = 1
launch_type = "FARGATE"
network_configuration {
subnets = var.subnet_ids
assign_public_ip = true
security_groups = [
"sg-098d8e07dc8df4f85"
]
}
load_balancer {
target_group_arn = aws_lb_target_group.app_target_group.arn
container_name = var.app_name
container_port = 3000
}
}
The remaining Application Load Balancer, Target Group, and Listener resources can remain unchanged from the original configuration.
terraform.tfvars
Provide the environment-specific values.
Replace the placeholders with values from your AWS environment.
region = "ap-south-1"
ecs_cluster_name = "cluster-name"
app_name = "app_name"
vpc_id = "vpc-id"
subnet_ids = [
"subnet-id-1",
"subnet-id-2"
]
The terraform.tfvars file separates configuration values from infrastructure code, making deployments easier to manage across development, staging, and production environments.
Step 3: Configure GitHub Actions for Continuous Deployment
GitHub Actions is GitHub's built-in Continuous Integration and Continuous Deployment (CI/CD) platform that automates software workflows directly from your repository.
In this project, GitHub Actions automates the deployment process by:
- Building the Docker image
- Pushing the image to Amazon Elastic Container Registry (Amazon ECR)
- Provisioning infrastructure with Terraform
- Deploying the application to Amazon ECS Fargate
Whenever code is pushed to the main branch, the deployment workflow is automatically triggered.
Important: Include only one architecture diagram illustrating the GitHub Actions workflow (GitHub โ GitHub Actions โ Amazon ECR โ Terraform โ Amazon ECS Fargate). Individual GitHub interface screenshots are unnecessary.
Create the Workflow Directory
Inside the project root, create the following directory structure:
.github/
โโโ workflows/
โโโ deploy.yml
deploy.yml
Create a workflow file named deploy.yml.
name: Deploy to ECS
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
- name: Log in to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push Docker image
env:
ECR_REGISTRY: 567738737859.dkr.ecr.ap-south-1.amazonaws.com
ECR_REPOSITORY: hello-world-app
IMAGE_TAG: latest
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Install Terraform
uses: hashicorp/setup-terraform@v1
with:
terraform_version: 1.0.0
- name: Terraform Init
run: terraform init
working-directory: ./terraform
- name: Terraform Apply
run: terraform apply -auto-approve
working-directory: ./terraform
Note: In a production environment, avoid running
terraform destroyas part of the deployment workflow unless it is specifically intended for temporary infrastructure.
Workflow Overview
The GitHub Actions workflow performs the following tasks:
- Checks out the latest application source code.
- Configures Docker Buildx.
- Authenticates with AWS.
- Logs in to Amazon ECR.
- Builds the Docker image.
- Pushes the image to Amazon ECR.
- Initializes Terraform.
- Applies the Terraform configuration to provision or update the infrastructure.
Step 4: Create an Amazon ECR Repository
Amazon Elastic Container Registry (Amazon ECR) is AWS's managed container image registry.
Before deploying the application, create an ECR repository to store the Docker image.
Create the Repository
Using the AWS CLI:
aws ecr create-repository \
--repository-name hello-world-app
After the repository is created, Amazon ECR returns details including:
- Repository URI
- Repository ARN
- Registry ID
- Creation timestamp
The repository URI will later be used by GitHub Actions to push Docker images.
Push Docker Images
The GitHub Actions workflow created earlier automatically performs the following operations:
- Builds the Docker image.
- Tags the image.
- Authenticates with Amazon ECR.
- Pushes the image to the repository.
No additional manual Docker commands are required after the workflow has been configured.
Step 5: Configure GitHub Secrets
AWS credentials should never be stored directly in source code.
Instead, GitHub provides encrypted repository secrets that are securely accessed during workflow execution.
Navigate to:
Repository โ Settings โ Secrets and variables โ Actions
Create the following secrets:
| Secret | Description |
|---|---|
| AWS_ACCESS_KEY_ID | IAM Access Key |
| AWS_SECRET_ACCESS_KEY | IAM Secret Access Key |
| AWS_DEFAULT_REGION | AWS deployment region |
The workflow automatically retrieves these values during deployment.
This approach improves security by preventing sensitive credentials from being committed to the repository.
Step 6: Push the Project to GitHub
Initialize the Repository
cd hello-world-app
git init
git remote add origin \
https://github.com/your-username/hello-world-app.git
git add .
git commit -m "Initial commit: Node.js app, Dockerfile, and Terraform configuration"
git branch -M main
git push -u origin main
Add the GitHub Actions Workflow
Create the workflow directory.
mkdir -p .github/workflows
Create the deployment workflow.
cat \<\<EOF > .github/workflows/deploy.yml
# Add the GitHub Actions workflow content here
EOF
Commit the workflow.
git add .github/workflows/deploy.yml
git commit -m "Add GitHub Actions workflow for ECS deployment"
git push
What Happens Next?
After the code is pushed to the main branch:
- GitHub Actions automatically starts the workflow.
- Docker builds the application image.
- The image is pushed to Amazon ECR.
- Terraform provisions or updates the AWS infrastructure.
- Amazon ECS Fargate launches the latest containerized application.
At this point, the entire deployment processโfrom source code commit to application deploymentโis fully automated using GitHub Actions, Terraform, Docker, Amazon ECR, and Amazon ECS Fargate.
Step 7: Configure the Terraform Backend
For production deployments, it's recommended to store the Terraform state file in a remote backend instead of keeping it locally. A remote backend enables teams to collaborate safely while maintaining a single source of truth for the infrastructure state.
Why Use a Remote Backend?
Using an Amazon S3 backend provides several advantages:
- Consistent State โ Ensures all team members use the same Terraform state file.
- State Locking โ Prevents multiple users from modifying infrastructure simultaneously when combined with DynamoDB state locking.
- Backup and Versioning โ Amazon S3 supports object versioning for state recovery.
- Improved Security โ Access to the state file can be controlled using AWS IAM policies.
Configure an Amazon S3 Backend
Update the main.tf file to configure Terraform to store its state remotely.
terraform {
backend "s3" {
bucket = "pavan-your-terraform-state-bucket"
key = "ecs/hello-world-app/terraform.tfstate"
region = "ap-south-1"
}
}
Replace the bucket name with your own Amazon S3 bucket before initializing Terraform.
Understanding terraform.tfstate
The terraform.tfstate file records the current state of the infrastructure managed by Terraform.
Terraform automatically creates and updates this file whenever commands such as the following are executed:
terraform init
terraform plan
terraform apply
Because the backend is configured to use Amazon S3, the state file is stored remotely at:
Bucket:
pavan-your-terraform-state-bucket
Object:
ecs/hello-world-app/terraform.tfstate
This eliminates the need to manually manage the state file while improving collaboration and disaster recovery.
Step 8: Deploy the Application
With the application, Docker image, Terraform configuration, Amazon ECR repository, and GitHub Actions workflow in place, you're ready to deploy the application to Amazon ECS Fargate.
Verify the Configuration
Before deployment, confirm that the following components have been configured correctly:
- Node.js application
- Dockerfile
- Terraform configuration
- Amazon ECR repository
- GitHub Actions workflow
- GitHub Secrets
- AWS credentials
- Terraform backend (optional but recommended)
Push the Latest Changes
Commit your changes locally.
git add .
git commit -m "Deploy Node.js application to Amazon ECS Fargate"
git push origin main
Pushing to the main branch automatically triggers the GitHub Actions workflow.
Monitor the GitHub Actions Workflow
Open your GitHub repository.
Navigate to:
Actions โ Deploy to ECS
The workflow automatically executes the deployment pipeline.
During execution, GitHub Actions performs the following steps:
- Checks out the source code.
- Builds the Docker image.
- Pushes the image to Amazon ECR.
- Initializes Terraform.
- Applies the Terraform configuration.
- Updates the Amazon ECS Fargate service.
Review the Workflow Logs
GitHub Actions provides real-time logs for every stage.
Review the logs to verify:
- Docker image build
- Amazon ECR authentication
- Docker image push
- Terraform execution
- ECS deployment
Any warnings or errors can be investigated directly from the workflow logs.
Verify the Deployment
After the workflow completes successfully:
- Open the Amazon ECS Console.
- Verify that the ECS Service is running.
- Confirm that the task status is Running.
- Open the Application Load Balancer DNS name (or application endpoint) in a browser.
If everything has been configured correctly, the Node.js application should be accessible.
Important: Include a single screenshot showing the successful GitHub Actions workflow or the deployed application running through the ECS Load Balancer. This final verification image is sufficient for this section.
Conclusion
In this guide, you successfully automated the deployment of a Node.js application to Amazon ECS Fargate using Terraform and GitHub Actions.
The solution combines modern DevOps practices to create a reliable and repeatable deployment pipeline.
Key Benefits
- GitHub Actions automates the build and deployment workflow whenever code is pushed to the repository.
- Terraform provisions and manages AWS infrastructure using Infrastructure as Code (IaC), ensuring consistency across environments.
- Amazon ECS Fargate removes the need to manage servers while providing scalable container orchestration.
- Amazon ECR securely stores and manages Docker container images.
By integrating these technologies, you establish a production-ready deployment pipeline that is scalable, secure, and easy to maintain, providing a strong foundation for deploying modern cloud-native applications on AWS.