Skip to main content

Command Palette

Search for a command to run...

Zero-Touch Deployment: My CI/CD Journey with Node.js, Docker, and Jenkins

You need to have basic knowledge of AWS, Docker, Jenkins before starting this project.

Published
β€’5 min read
Zero-Touch Deployment: My CI/CD Journey with Node.js, Docker, and Jenkins
S

I am a third year B.Tech graduate from VIT Bhopal University. I love to code in Java and and skilling up myself in the field of DevOps.

Continuous Integration and Continuous Deployment (CI/CD) are essential practices in modern software development that help deliver reliable code rapidly. In my recent project, I automated the entire lifecycle of a Node.js web app β€” from source code to deployment β€” using Docker, Jenkins, and AWS EC2.

In this post, I’ll walk you through the setup and architecture of my CI/CD pipeline, the Docker containerization process, and the deployment strategy on a free-tier AWS EC2 instance.

Project Overview

The project is a Node.js application available on GitHub: Code-Whispers. The app runs on port 8001 and serves its main API at /api/v2/url.

My goal was to automate the process of building, packaging, and deploying this app with zero manual intervention.

Pre-task: Containerizing the Node.js App with Docker

Before diving into deployment, I designed a Dockerfile to containerize the app and its dependencies:

# Use an official Node.js runtime as a parent image
FROM node:18-slim

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install project dependencies
RUN npm install

# Bundle app source
COPY . .

# Make port 8001 available to the world outside this container
EXPOSE 8001

# Define environment variable for the port
ENV PORT=8001

# Run index.js when the container launches
CMD [ "node", "index.js" ]

Docker binds the application and its dependencies into a single container that can run anywhere.

Adding Jenkins to Docker group

sudo usermod -aG docker jenkins

Running on AWS EC2 Free Tier

I used a t2.micro EC2 instance (Ubuntu).

  • Update Ubuntu: sudo apt update

  • Install Docker: sudo apt install docker.io

  • Install Jenkins (refer to the Jenkins install guide).

  • Open necessary ports in the AWS Security Group.

Login into Jenkins

Navigate to http://<ec2-public-ip>:8080

Register and log in.

  1. Click New Item.

  2. Name your project.

  3. Choose Pipeline as the type.

  4. Paste your Jenkinsfile into the Pipeline configuration.

Building a Jenkins Pipeline

The pipeline is triggered on every push to the main branch using GitHub webhooks.

Pipeline Stages

  1. Clean Up: Removes dangling Docker images to free disk space on the Jenkins agent.

  2. Code Checkout: Clones the repository from GitHub.

  3. Build and Push:

    • Uses Jenkins credentials to securely access Docker Hub and an .env file.

    • Builds the Docker image tagged as username/code-whispers-app:latest.

    • Pushes the image to Docker Hub.

  4. Deploy:

    • Removes any existing container on the AWS EC2 instance.

    • Pulls the latest image from Docker Hub.

    • Runs the container with environment variables loaded from the .env file, exposing port 8001.

Jenkinsfile excerpt:

pipeline {
    agent any
    stages {
        stage('Clean Up') {
            steps {
                script {
                    // Remove dangling images (untagged)
                    sh 'docker image prune -f'
                }
            }
        }

        stage("Code") {
            steps {
                git url: "https://github.com/CoolSrj06/code-whispers.git", branch: "main"
            }
        }

        stage("Build and Push") {
            steps {
                withCredentials([
                    usernamePassword(credentialsId: "dockerHub", usernameVariable: "dockerHubUser", passwordVariable: "dockerHubPass"),
                    file(credentialsId: "ENV_FILE_ID", variable: "ENV_FILE")
                ]) {
                    script {
                        def dockerImage = "${dockerHubUser}/code-whispers-app:latest"

                        sh 'rm -f .env'
                        // Copy the .env file into the workspace
                        sh '''cp $ENV_FILE .env'''
                        //sh 'cat .env' // Optional: for debugging (remove in production!)

                        // Login to Docker Hub
                        sh '''
                            echo "$dockerHubPass" | docker login -u "$dockerHubUser" --password-stdin
                        '''

                        // Build and push Docker image
                        sh "docker build . -t ${dockerImage}"
                        sh "docker push ${dockerImage}"
                    }
                }
            }
        }

        stage("Deploy") {
            steps {
                withCredentials([
                    usernamePassword(credentialsId: "dockerHub", usernameVariable: "dockerHubUser", passwordVariable: "dockerHubPass"),
                    file(credentialsId: "ENV_FILE_ID", variable: "ENV_FILE")
                ]) {
                    script {
                        def dockerImage = "${dockerHubUser}/code-whispers-app:latest"

                        // Copy .env file again
                        sh 'rm -f .env'
                        sh 'cp $ENV_FILE .env'

                        // Optional: Login to Docker Hub again if needed
                        sh '''
                            echo "$dockerHubPass" | docker login -u "$dockerHubUser" --password-stdin
                        '''

                        // Remove existing container (if running)
                        sh "docker rm -f code-whispers-app || true"

                        // Pull the latest image
                        sh "docker pull ${dockerImage}"

                        // Run the container
                        sh "docker run -d --env-file .env -p 8001:8001 --name code-whispers-app ${dockerImage}"
                    }
                }
            }
        }
    }

    post {
        always {
            sh 'docker logout'
            cleanWs()
        }
    }
}

πŸ”’ Secure Handling of Credentials

  • Docker Hub username and password are stored as Jenkins credentials.

  • The .env file with environment variables is securely injected via Jenkins credentials binding.

  • The pipeline ensures these sensitive files are never exposed in logs or the workspace permanently.

Pro Tip: Always use Jenkins credentials for security best practices.

I am also sharing my configuration setting.

Once all the Pipeline configuration is set, you can click Build Now.

Monitor the build logs and navigate to: http://<ec2-public-ip>:8001/api/v2/url/ to see your app in action.

Benefits and Learnings

  • Automation: One push to GitHub triggers a full build, test (planned), push, and deploy cycle.

  • Repeatability: Identical environments from developer machine to production.

  • Efficiency: Clean-up step keeps Jenkins agent healthy.

  • Security: Credentials and environment variables are managed securely.

Challenges: Handling .env securely and ensuring zero downtime deployment on EC2 were tricky but resolved with the current approach.

Try It Yourself

Check out the code here: https://github.com/CoolSrj06/code-whispers.git

Feel free to clone, build, and customize the pipeline for your own projects!

Conclusion

Implementing CI/CD with Docker and Jenkins on AWS EC2 is a game-changer for automating deployments. It reduces manual work, improves reliability, and sets the stage for continuous improvement.

Have questions or feedback? Let’s connect in the comments! πŸš€