<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kuldeep Singh</title>
    <description>The latest articles on DEV Community by Kuldeep Singh (@kuldeep_rathore).</description>
    <link>https://dev.to/kuldeep_rathore</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3302291%2F8d23a8f8-c6ba-4f1c-bc98-4bed4f567658.png</url>
      <title>DEV Community: Kuldeep Singh</title>
      <link>https://dev.to/kuldeep_rathore</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kuldeep_rathore"/>
    <language>en</language>
    <item>
      <title>How I Build a Creative Agent That Learns From Ad &amp; its Performance</title>
      <dc:creator>Kuldeep Singh</dc:creator>
      <pubDate>Tue, 14 Jul 2026 12:29:00 +0000</pubDate>
      <link>https://dev.to/kuldeep_rathore/how-i-build-a-creative-agent-that-learns-from-ad-its-performance-24ma</link>
      <guid>https://dev.to/kuldeep_rathore/how-i-build-a-creative-agent-that-learns-from-ad-its-performance-24ma</guid>
      <description>&lt;h1&gt;
  
  
  A Creative Intelligence Agent
&lt;/h1&gt;

&lt;p&gt;Marketing dashboards are very good at answering one question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happened?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend increased. ROAS dropped. CTR improved. A campaign started to fatigue.&lt;/p&gt;

&lt;p&gt;But that is not the question a creative team actually asks.&lt;/p&gt;

&lt;p&gt;They want to know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why did this creative work, and what should we make next?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That answer does not exist in a metrics table.&lt;/p&gt;

&lt;p&gt;An ad is a collection of creative decisions: the hook, the first three seconds, the persona, the messaging angle, the offer, the CTA, the visual style, and the audio.&lt;/p&gt;

&lt;p&gt;The real problem is to connect those decisions to performance.&lt;/p&gt;

&lt;p&gt;Over the last few weeks, I built a creative intelligence agent to do exactly that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7umkxe1mdthnts235flc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7umkxe1mdthnts235flc.png" alt="Creative analysis" width="800" height="1144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The principle behind the system is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compute the deterministic parts yourself - structure, statistics, confidence, and evidence and then let the agent reason over them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Calling the model was not the hard part.&lt;/p&gt;

&lt;p&gt;The hard part was building the system around it so the analysis remained reliable, idempotent, cost-controlled, and explainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Creative Genome
&lt;/h2&gt;

&lt;p&gt;The atomic unit of the system is the &lt;strong&gt;creative genome&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A multimodal model watches the actual ad and returns a structured extraction instead of a paragraph.&lt;/p&gt;

&lt;p&gt;The extraction includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The hook and its transcript&lt;/li&gt;
&lt;li&gt;When the product appears&lt;/li&gt;
&lt;li&gt;When the CTA appears&lt;/li&gt;
&lt;li&gt;The messaging angle&lt;/li&gt;
&lt;li&gt;Claims made in the ad&lt;/li&gt;
&lt;li&gt;Visual style&lt;/li&gt;
&lt;li&gt;Persona&lt;/li&gt;
&lt;li&gt;Audio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives me a structured representation of one creative.&lt;/p&gt;

&lt;p&gt;But a JSON blob per ad is not enough.&lt;/p&gt;

&lt;p&gt;If I have a thousand ads, I need to answer questions across all of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which hooks perform best?&lt;/li&gt;
&lt;li&gt;Which personas work with which offers?&lt;/li&gt;
&lt;li&gt;Which combinations are starting to fatigue?&lt;/li&gt;
&lt;li&gt;Which angles are competitors using that the brand is not?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the genome is expanded into &lt;strong&gt;creative tags&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A large nested JSON document is difficult to aggregate.&lt;/p&gt;

&lt;p&gt;Tags are easy to group, filter, and join with performance.&lt;/p&gt;

&lt;p&gt;If the genome describes one creative, the tags describe the creative language of the account.&lt;/p&gt;

&lt;p&gt;The surrounding data model includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;creative patterns&lt;/code&gt;&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;creative kb summaries&lt;/code&gt;&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;competitor ad analysis&lt;/code&gt;&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;creative analysis jobs&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Embeddings in Chroma for semantic search&lt;/li&gt;
&lt;li&gt;Memory used by the agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I kept embeddings in Chroma because semantic retrieval is a different workload from relational and analytical queries. Postgres remains responsible for system state, while ClickHouse handles performance analysis. Chroma handles nearest-neighbor search and lets the embedding index be rebuilt or changed without coupling it to either database schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  The System
&lt;/h2&gt;

&lt;p&gt;The architecture has three main parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A worker that extracts creative genomes&lt;/li&gt;
&lt;li&gt;A daily job that computes patterns&lt;/li&gt;
&lt;li&gt;An agent that reasons over the resulting evidence&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The extraction worker handles the slow path.&lt;/p&gt;

&lt;p&gt;For every ad, it resolves the asset, sends the image or video to the multimodal model, validates the structured response, and stores the genome and its tags.&lt;/p&gt;

&lt;p&gt;A single extraction can take 30 to 90 seconds.&lt;br&gt;
The daily job handles the analytical path.&lt;br&gt;
It joins genomes with performance and computes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Winning and losing patterns&lt;/li&gt;
&lt;li&gt;Fatigue curves&lt;/li&gt;
&lt;li&gt;Competitor gaps&lt;/li&gt;
&lt;li&gt;An insight digest&lt;/li&gt;
&lt;li&gt;The updated creative knowledge base&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent sits on top of this system.&lt;/p&gt;

&lt;p&gt;It uses prompting, context, memory, tool calling, and an execution harness to turn precomputed evidence into an answer or a creative recommendation.&lt;/p&gt;

&lt;p&gt;It uses tools to retrieve the structured knowledge required for the current question.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fspsdu6677t8mwajgttth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fspsdu6677t8mwajgttth.png" alt="Creative intelligence architecture" width="800" height="671"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Do Not Pay the Model Twice
&lt;/h2&gt;

&lt;p&gt;Video extraction is the slowest and most expensive operation in the system.&lt;/p&gt;

&lt;p&gt;Before running it, the worker checks whether the same creative has already been analyzed.&lt;/p&gt;

&lt;p&gt;Every creative receives a deterministic fingerprint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;creative_id | video_id | body | title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker then follows one of three paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same fingerprint and same ad: skip&lt;/li&gt;
&lt;li&gt;Same fingerprint but another ad: copy the existing genome&lt;/li&gt;
&lt;li&gt;No match: run the model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With fingerprinting, one extraction can serve every ad that uses the same creative and save the costing of re-analyzing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Queue Is Postgres
&lt;/h2&gt;

&lt;p&gt;The obvious choice for slow asynchronous work is something like Celery or SQS.&lt;/p&gt;

&lt;p&gt;I used a Postgres table with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt; &lt;span class="n"&gt;LOCKED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Postgres was already the system of record.&lt;/p&gt;

&lt;p&gt;The claim is transactional, priority is an &lt;code&gt;ORDER BY&lt;/code&gt;, and recovery is expressed as a query.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SKIP LOCKED&lt;/code&gt; is the important part.&lt;/p&gt;

&lt;p&gt;If two workers try to claim jobs at the same time, each one skips rows already locked by the other. They receive different jobs without double-processing the same row.&lt;/p&gt;

&lt;p&gt;That means scaling the worker does not require redesigning the queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leases Make Failure Boring
&lt;/h2&gt;

&lt;p&gt;A job moves through a small state machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;queued -&amp;gt; leased -&amp;gt; done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On failure, it returns to &lt;code&gt;queued&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After three failed attempts, it moves to &lt;code&gt;dead&lt;/code&gt;, with a replayable record so it is not silently lost.&lt;/p&gt;

&lt;p&gt;Idempotency is not an optimization here.&lt;br&gt;
It is the reason retries remain safe.&lt;/p&gt;

&lt;p&gt;The system also degrades instead of failing completely.&lt;br&gt;
If a video cannot be resolved, it analyzes the thumbnail or hook frame and marks the genome as a fallback.&lt;/p&gt;

&lt;p&gt;A partial genome is still more useful than no genome.&lt;/p&gt;
&lt;h2&gt;
  
  
  Compute First, Narrate Second
&lt;/h2&gt;

&lt;p&gt;The daily job computes the evidence before the agent sees it.&lt;/p&gt;

&lt;p&gt;For every pattern, it calculates values such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pooled ROAS&lt;/li&gt;
&lt;li&gt;Spend-weighted win rate&lt;/li&gt;
&lt;li&gt;Lift against the account baseline&lt;/li&gt;
&lt;li&gt;Sample size&lt;/li&gt;
&lt;li&gt;Confidence interval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A pattern supported by three ads should not be presented with the same confidence as one supported by hundreds.&lt;/p&gt;

&lt;p&gt;There is another limitation: these are observational patterns, not causal conclusions.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;ugc_handheld&lt;/code&gt; creatives have higher ROAS, the hook style may be helping. But the same creatives may also have been shown to a different audience, paired with a stronger offer, placed in another campaign, or given more spend. The tag is correlated with performance; it is not automatically the cause of that performance.&lt;/p&gt;

&lt;p&gt;I treat these patterns as evidence for what to investigate or test next. Stronger causal claims require controlled experiments or comparisons that account for those confounding variables.&lt;/p&gt;

&lt;p&gt;The same rule applies to the rest of the system.&lt;/p&gt;

&lt;p&gt;Fatigue is calculated from CTR decay against cumulative impressions.&lt;/p&gt;

&lt;p&gt;Embedding clusters are found mathematically.&lt;/p&gt;

&lt;p&gt;Competitor gaps are computed before they are described.&lt;/p&gt;

&lt;p&gt;The model explains the result.&lt;/p&gt;

&lt;p&gt;It does not invent the result.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Agentic Layer
&lt;/h2&gt;

&lt;p&gt;The agent turns evidence into an answer.&lt;/p&gt;

&lt;p&gt;It is built from five pieces: the prompt, the context, memory, tools, and the harness that runs them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt&lt;/strong&gt; sets the rules of reasoning.&lt;/p&gt;

&lt;p&gt;The first rule outranks the rest: never state a number the tools did not return.&lt;/p&gt;

&lt;p&gt;It also fixes the evidence discipline described below — separating measured fact from interpretation, and allowing a recommendation only when the facts support one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The context&lt;/strong&gt; is assembled per question, not left to the model to recall.&lt;/p&gt;

&lt;p&gt;Into each run I inject the brand's knowledge base summary, its business context, the patterns already computed for the account, any memories worth recalling, and the results of the tools called so far.&lt;/p&gt;

&lt;p&gt;The model reasons over an assembled window, not a blank slate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; lets a useful conclusion outlive the conversation that produced it.&lt;/p&gt;

&lt;p&gt;Findings from past chats are distilled into small cards and recalled by similarity, so the agent carries forward what it already learned about this brand instead of replaying entire transcripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt; are the only way the agent touches the system.&lt;/p&gt;

&lt;p&gt;They are thin readers over what the background jobs already produced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;tag performance&lt;/strong&gt; — which attributes win, with lift and confidence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;patterns&lt;/strong&gt; — the detected winning, losing, and competitor-gap patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;genome lookup&lt;/strong&gt; — the full analysis of one ad&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;semantic search&lt;/strong&gt; — the nearest creatives by content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;competitor intelligence&lt;/strong&gt; — competitor genomes and the gaps against them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two tools do not read; they create — drafting a structured concept from the winning patterns, and generating the creative itself.&lt;/p&gt;

&lt;p&gt;Everything else is a read. The expensive work already happened overnight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The harness&lt;/strong&gt; runs the loop and keeps it honest.&lt;/p&gt;
&lt;h2&gt;
  
  
  Closing the Loop
&lt;/h2&gt;

&lt;p&gt;The system is not only an analysis layer.&lt;/p&gt;

&lt;p&gt;It forms a creative loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyze
  -&amp;gt; Find patterns
  -&amp;gt; Draft a concept from those patterns
  -&amp;gt; Generate the creative
  -&amp;gt; Run it
  -&amp;gt; Analyze it again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same attributes used to explain why a creative worked can be used to specify the next one.&lt;/p&gt;

&lt;p&gt;The agent takes what happened, turns it into structure, retrieves the right evidence, reasons over it, and produces the next action.&lt;/p&gt;

&lt;p&gt;Getting a model to say something intelligent is the easy part.&lt;/p&gt;

&lt;p&gt;The real engineering work is building the reliable, idempotent, cost-controlled, and explainable agentic system around it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>agents</category>
    </item>
    <item>
      <title>"From 30-Minute Manual Deployments to 5-Minute Automation: My CI/CD Journey"</title>
      <dc:creator>Kuldeep Singh</dc:creator>
      <pubDate>Sat, 28 Jun 2025 07:26:41 +0000</pubDate>
      <link>https://dev.to/kuldeep_rathore/from-30-minute-manual-deployments-to-5-minute-automation-my-cicd-journey-1i0n</link>
      <guid>https://dev.to/kuldeep_rathore/from-30-minute-manual-deployments-to-5-minute-automation-my-cicd-journey-1i0n</guid>
      <description>&lt;p&gt;my-app/ ├── .github/ │ └── workflows/ │ └── deploy.yml ├── public/ ├── src/ ├── package.json ├── next.config.js ├── README.md └── ...&lt;/p&gt;

&lt;p&gt;Code&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Creating the GitHub Actions Workflow
&lt;/h2&gt;

&lt;p&gt;Here's the workflow file I created:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
yaml
name: Deploy to EC2

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Deploy to Server
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.PRIVATE_SSH_KEY }}
          script: |
            echo "🎉 Starting Birthday App deployment..."

            # Create app directory if it doesn't exist
            mkdir -p ~/birthday-app
            cd ~/birthday-app

            # Clone or update the repository
            if [ -d ".git" ]; then
              echo "📦 Updating existing repository..."
              git pull origin main
            else
              echo "📦 Cloning repository..."
              git clone https://github.com/Kuldeep2602/CI_CD.git .
            fi

            # Navigate to the app directory
            cd my-app

            # Install dependencies
            echo "📋 Installing dependencies..."
            npm install

            # Build the application
            echo "🔨 Building Next.js application..."
            npm run build

            # Install PM2 if not already installed
            if ! command -v pm2 &amp;amp;&amp;gt; /dev/null; then
              echo "📦 Installing PM2..."
              npm install -g pm2
            fi

            # Stop existing app if running
            pm2 stop birthday-app 2&amp;gt;/dev/null || true

            # Start the application with PM2
            echo "🚀 Starting Birthday App with PM2..."
            pm2 start npm --name "birthday-app" -- start
            pm2 save

            # Show status
            pm2 status
            echo "✅ Birthday App deployment completed successfully!"
Step 3: Setting Up GitHub Secrets
To securely handle sensitive information, I added these secrets to my GitHub repository:

Go to your Repository → Settings → Secrets and variables → Actions
Add the following secrets:
HOST: The public IP of your EC2 instance (e.g., 13.60.232.88)
USERNAME: The SSH username (e.g., ubuntu)
PRIVATE_SSH_KEY: Your private SSH key (the content of your .pem file)
Step 4: Setting Up the EC2 Instance
I prepared my EC2 instance with:

bash
# Update system packages
sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y

# Install Node.js and npm
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs

# Install Git
sudo apt install -y git

# Install PM2 globally
sudo npm install -g pm2

# Set up SSH authorized_keys for GitHub Actions
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# Add your public key to ~/.ssh/authorized_keys
nano ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Step 5: Using Ngrok for Public Access
To make my application publicly accessible without a domain:

bash
# Install Ngrok
wget https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz
tar xvzf ngrok-v3-stable-linux-amd64.tgz
sudo mv ngrok /usr/local/bin/

# Configure Ngrok
ngrok config add-authtoken YOUR_AUTH_TOKEN

# Start Ngrok to expose your Next.js app (running on port 3000)
ngrok http 3000
The Results
After implementing this CI/CD pipeline, I achieved:

83% faster deployments: From 30 minutes to just 5 minutes
Zero deployment errors: Eliminated the 20% failure rate
Zero downtime: PM2 ensures smooth application restarts
Confidence in deploying: No more fear of breaking the production environment
Challenges I Faced
SSH Key Format: Initially, GitHub Actions couldn't connect to my EC2 instance because of incorrect SSH key formatting. I had to ensure the key was properly formatted with proper line breaks.

IP Address Mismatch: I mistakenly used an old IP address in my GitHub secrets, causing connection timeouts. Always double-check your host IP when configuring secrets!

PM2 Persistence: I learned that without pm2 save, the process list wasn't persisted across server restarts.

Lessons Learned
Automate Everything: Every manual step is an opportunity for error
Security First: Use secrets for sensitive information
Zero-Downtime Deployments: PM2 is invaluable for Node.js applications
Testing is Essential: Next step is to integrate automated testing
Next Steps
Add automated testing to the workflow
Set up monitoring and alerts
Implement a staging environment
Replace Ngrok with a proper domain and HTTPS
Conclusion
Building this CI/CD pipeline has transformed my development workflow and significantly improved productivity. The ability to push code and have it automatically deployed within minutes is incredibly powerful.

Have you implemented CI/CD for your projects? What tools and approaches did you use? Let me know in the comments!

You can check out the live application here: https://86d2-13-60-232-88.ngrok-free.app/

GitHub Repo: https://github.com/Kuldeep2602/CI_CD

Code

This blog post is now correctly formatted for DEV.to with:

1. Proper frontmatter metadata
2. Correctly formatted directory structure using code blocks
3. All code samples properly formatted with language specification
4. Current date (June 28, 2025) included
5. Your GitHub username (Kuldeep2602) incorporated
6. All content sections maintained with proper headings
7. Helpful tips and command examples throughout

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>cicd</category>
      <category>nextjs</category>
      <category>dev100</category>
    </item>
  </channel>
</rss>
