<?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: Balamurugan pandian</title>
    <description>The latest articles on DEV Community by Balamurugan pandian (@socialcoding).</description>
    <link>https://dev.to/socialcoding</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%2F3974790%2Fde4329f3-6455-4b5b-9e1b-3dcebab45cc5.png</url>
      <title>DEV Community: Balamurugan pandian</title>
      <link>https://dev.to/socialcoding</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/socialcoding"/>
    <language>en</language>
    <item>
      <title>Stop Doing ClickOps: A Practical Guide to Terraform and GitHub Actions</title>
      <dc:creator>Balamurugan pandian</dc:creator>
      <pubDate>Tue, 11 Aug 2026 16:59:36 +0000</pubDate>
      <link>https://dev.to/socialcoding/stop-doing-clickops-a-practical-guide-to-terraform-and-github-actions-7g5</link>
      <guid>https://dev.to/socialcoding/stop-doing-clickops-a-practical-guide-to-terraform-and-github-actions-7g5</guid>
      <description>&lt;p&gt;We have all been there. It is late on a Friday afternoon, and you are manually clicking through a cloud provider console trying to remember which specific security group you need to attach to a new web server. This manual process is affectionately known in the industry as “ClickOps”, and it is a complete nightmare for scaling a tech team.&lt;/p&gt;

&lt;p&gt;When you rely on human memory and manual clicks to build your infrastructure, mistakes are guaranteed. Environments drift out of sync. Deployments become scary events that require downtime and prayer.&lt;/p&gt;

&lt;p&gt;Today, we are going to fix that. We will look at the foundation of modern DevOps by automating infrastructure using Terraform and setting up a continuous integration and continuous deployment (CI/CD) pipeline with GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Infrastructure as Code with Terraform&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of clicking buttons in a web UI, Infrastructure as Code allows us to write configuration files that describe exactly what our servers and databases should look like. Terraform is the industry standard for this.&lt;/p&gt;

&lt;p&gt;Terraform uses a declarative language. You simply tell it what you want the end state to be, and it figures out the API calls required to make it happen.&lt;/p&gt;

&lt;p&gt;Let us look at a basic example. Suppose we need to provision a simple Ubuntu server on AWS to host a new application. Here is what that looks like in Terraform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.tf&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Define the cloud provider&lt;/span&gt;
&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;us-east-1&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Look up the latest Ubuntu AMI&lt;/span&gt;
&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;ubuntu&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;most_recent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;owners&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="mi"&gt;099720109477&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Canonical&lt;/span&gt;

&lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;
&lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;ubuntu&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;hvm-ssd&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ubuntu-jammy-22&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="nx"&gt;-amd64-server-&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Define the actual server resource&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;web_server&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;ami&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ubuntu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;t3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;micro&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;

&lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;Production-Web-App&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;
&lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;Prod&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code is highly readable. We define AWS as our provider, search for the latest Ubuntu machine image, and then declare an EC2 instance.&lt;/p&gt;

&lt;p&gt;To deploy this, you simply open your terminal and run two commands:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;- &lt;code&gt;terraform init&lt;/code&gt; to download the AWS plugins.&lt;/li&gt;
&lt;li&gt;- &lt;code&gt;terraform apply&lt;/code&gt; to review the plan and build the server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If someone accidentally deletes this server, you do not have to panic. You just run terraform apply again, and your infrastructure is restored exactly as it was defined in the code. Your code repository is now the single source of truth for your infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Automating Deployments with GitHub Actions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that our server exists, how do we get our application code onto it? Manually logging into a server via SSH to pull code and restart services is just as bad as ClickOps. We need a pipeline.&lt;/p&gt;

&lt;p&gt;GitHub Actions allows you to run automated workflows every time a specific event happens in your repository (like merging a pull request).&lt;br&gt;
Here is an example of a workflow file that tests a Node.js application and then deploys it. You place this file inside your repository at &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Node.js CI/CD Pipeline&lt;/span&gt;

&lt;span class="c1"&gt;# Trigger this workflow when code is pushed to the main branch&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;“main”&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;build-and-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;— name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out the repository&lt;/span&gt;
&lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Node.js&lt;/span&gt;
&lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;‘20’&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run unit tests&lt;/span&gt;
&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# Only run the deploy job if the build-and-test job succeeds&lt;/span&gt;
&lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-and-test&lt;/span&gt;
&lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;— name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Production Server&lt;/span&gt;
&lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appleboy/ssh-action@master&lt;/span&gt;
&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.PROD_SERVER_IP }}&lt;/span&gt;
&lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_USER }}&lt;/span&gt;
&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_PRIVATE_KEY }}&lt;/span&gt;
&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;span class="s"&gt;cd /var/www/my-app&lt;/span&gt;
&lt;span class="s"&gt;git pull origin main&lt;/span&gt;
&lt;span class="s"&gt;npm install — production&lt;/span&gt;
&lt;span class="s"&gt;pm2 restart web-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Breaking Down the Pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This workflow is split into two distinct jobs.&lt;/p&gt;

&lt;p&gt;The build-and-test job spins up a fresh, isolated container on GitHub’s servers. It downloads your code, installs the required packages, and runs your test suite. If any test fails, the entire pipeline stops immediately. This prevents broken code from ever reaching your users.&lt;/p&gt;

&lt;p&gt;If the tests pass, the deploy job takes over. It uses a popular community action to securely SSH into the AWS server we built earlier with Terraform. It then runs a simple script to pull the latest code, install production dependencies, and restart the application manager.&lt;/p&gt;

&lt;p&gt;Notice how we use &lt;code&gt;${{ secrets.PROD_SERVER_IP }}&lt;/code&gt;. You should never hardcode sensitive information like IP addresses or SSH keys into your repository. GitHub provides a secure secrets manager to handle these variables safely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The DevOps Payoff&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Moving away from manual processes takes a little upfront effort. You have to learn the syntax of Terraform and figure out the quirks of YAML files for GitHub Actions.&lt;/p&gt;

&lt;p&gt;However, the payoff is massive. When you combine Infrastructure as Code with automated pipelines, your deployments become incredibly boring and highly predictable. You can release new features on a Friday afternoon with confidence, knowing the machines will handle the heavy lifting. In the world of DevOps, boring is exactly what you want.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>programming</category>
      <category>discuss</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
