<?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>Forem: Amol Mali</title>
    <description>The latest articles on Forem by Amol Mali (@devopswithamol).</description>
    <link>https://forem.com/devopswithamol</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3296828%2Fec0d3436-5f02-43d7-8d00-79f83639b22a.png</url>
      <title>Forem: Amol Mali</title>
      <link>https://forem.com/devopswithamol</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/devopswithamol"/>
    <language>en</language>
    <item>
      <title>Auto-Tag Docker Images with Semantic Versioning Using GitHub Actions</title>
      <dc:creator>Amol Mali</dc:creator>
      <pubDate>Sun, 29 Jun 2025 07:22:33 +0000</pubDate>
      <link>https://forem.com/devopswithamol/auto-tag-docker-images-with-semantic-versioning-using-github-actions-1g50</link>
      <guid>https://forem.com/devopswithamol/auto-tag-docker-images-with-semantic-versioning-using-github-actions-1g50</guid>
      <description>&lt;h1&gt;
  
  
  🚀 Auto-Tag Docker Images with Semantic Versioning Using GitHub Actions
&lt;/h1&gt;

&lt;p&gt;Docker images tagged with long SHA hashes are hard to track. Let's fix that.&lt;/p&gt;

&lt;p&gt;In this guide, you’ll learn how to &lt;strong&gt;automatically build and push Docker images&lt;/strong&gt; to Docker Hub using &lt;strong&gt;semantic version tags&lt;/strong&gt; like &lt;code&gt;v1.0.0&lt;/code&gt;, and optionally update &lt;code&gt;latest&lt;/code&gt; as well — all triggered directly by a &lt;code&gt;git tag&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧰 Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before getting started, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A working GitHub repository with a &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;Docker Hub account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Secrets:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DOCKER_USERNAME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DOCKER_PASSWORD&lt;/code&gt; or access token&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Semantic version tags in your repository (e.g., &lt;code&gt;git tag v1.2.3&lt;/code&gt;)&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 What We'll Automate
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Watch for pushes to tags like &lt;code&gt;v1.2.3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Extract the tag version from &lt;code&gt;GITHUB_REF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build the Docker image&lt;/li&gt;
&lt;li&gt;Tag it with both &lt;code&gt;v1.2.3&lt;/code&gt; and &lt;code&gt;latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Push both to Docker Hub&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚙️ GitHub Actions Workflow
&lt;/h2&gt;

&lt;p&gt;Here's a complete example of a workflow file to place at &lt;code&gt;.github/workflows/docker-release.yml&lt;/code&gt;:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
yaml
name: Release Docker Image

on:
  push:
    tags:
      - 'v*.*.*'  # Matches v1.0.0, v2.3.1 etc.

jobs:
  docker:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Extract tag version
        id: version
        run: echo "VERSION=${GITHUB_REF#refs/tags/}" &amp;gt;&amp;gt; $GITHUB_ENV

      - name: Log in to Docker Hub
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

      - name: Build Docker image
        run: |
          docker build -t ${{ secrets.DOCKER_USERNAME }}/your-app:${{ env.VERSION }} .
          docker tag ${{ secrets.DOCKER_USERNAME }}/your-app:${{ env.VERSION }} ${{ secrets.DOCKER_USERNAME }}/your-app:latest

      - name: Push Docker image
        run: |
          docker push ${{ secrets.DOCKER_USERNAME }}/your-app:${{ env.VERSION }}
          docker push ${{ secrets.DOCKER_USERNAME }}/your-app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>githubactions</category>
      <category>devops</category>
      <category>cicd</category>
      <category>semver</category>
    </item>
    <item>
      <title>Automate Server Deployments with GitHub Actions &amp; SSH</title>
      <dc:creator>Amol Mali</dc:creator>
      <pubDate>Sun, 29 Jun 2025 07:09:05 +0000</pubDate>
      <link>https://forem.com/devopswithamol/automate-server-deployments-with-github-actions-ssh-p24</link>
      <guid>https://forem.com/devopswithamol/automate-server-deployments-with-github-actions-ssh-p24</guid>
      <description>&lt;h2&gt;
  
  
  🚀 Automate Server Deployments with GitHub Actions &amp;amp; SSH
&lt;/h2&gt;

&lt;p&gt;Modern development teams want code changes to move from GitHub to production without friction. In this guide, we’ll extend our CI/CD pipeline to automatically deploy to a server using SSH—no manual steps required.&lt;/p&gt;

&lt;p&gt;This builds on the &lt;a href="https://dev.to/devopswithamol/cicd-pipeline-with-github-actions-from-code-to-deployment-3e65"&gt;previous blog&lt;/a&gt; where we created a pipeline to build and push Docker images using GitHub Actions.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧰 Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before getting started, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A Linux server with SSH access (e.g. DigitalOcean, AWS EC2, VPS)&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;Docker Hub account&lt;/a&gt; (if using Docker)&lt;/li&gt;
&lt;li&gt;SSH key pair added to your server’s &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;GitHub repository secrets:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SSH_HOST&lt;/code&gt;           # e.g., 192.168.1.100&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SSH_USER&lt;/code&gt;           # e.g., ubuntu&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SSH_PRIVATE_KEY&lt;/code&gt;    # No passphrase&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SSH_KNOWN_HOSTS&lt;/code&gt;    # Generated via &lt;code&gt;ssh-keyscan &amp;lt;server-ip&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔐 Security Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Generate SSH keys without passphrases specifically for GitHub Actions&lt;/li&gt;
&lt;li&gt;Only add the private key to GitHub Secrets&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;ssh-keyscan your-server-ip&lt;/code&gt; to safely populate &lt;code&gt;SSH_KNOWN_HOSTS&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔒 Avoid using root—prefer a user with limited sudo access if needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Why Use GitHub Actions for Deployment?
&lt;/h2&gt;

&lt;p&gt;By adding SSH deployment to your existing pipeline, you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid manual &lt;code&gt;scp&lt;/code&gt;, &lt;code&gt;git pull&lt;/code&gt;, or &lt;code&gt;docker run&lt;/code&gt; commands&lt;/li&gt;
&lt;li&gt;Trigger deployments automatically on code changes&lt;/li&gt;
&lt;li&gt;Keep all deployment logic under version control&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 CI/CD Workflow with SSH Deployment
&lt;/h2&gt;

&lt;p&gt;Here’s how the full pipeline works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Code is pushed to the &lt;code&gt;main&lt;/code&gt; branch&lt;/li&gt;
&lt;li&gt;GitHub Actions:

&lt;ul&gt;
&lt;li&gt;Builds the Python app&lt;/li&gt;
&lt;li&gt;Pushes a Docker image to Docker Hub&lt;/li&gt;
&lt;li&gt;SSHs into the server and deploys the latest Docker image&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  📋 Full GitHub Actions Workflow
&lt;/h2&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;CI/CD Pipeline&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="s"&gt;main&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&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="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;Checkout code&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@v3&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 Python&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-python@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;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.11'&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="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;

&lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&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&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="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;Checkout code&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@v3&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;Log in to Docker Hub&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;echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin&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;Build and Push Docker Image&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;docker build -t ${{ secrets.DOCKER_USERNAME }}/python-cicd:${{ github.sha }} .&lt;/span&gt;
        &lt;span class="s"&gt;docker push ${{ secrets.DOCKER_USERNAME }}/python-cicd:${{ github.sha }}&lt;/span&gt;

&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&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;docker&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="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 Server via SSH&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@v1&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.SSH_HOST }}&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.SSH_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;known_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_KNOWN_HOSTS }}&lt;/span&gt;
        &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;docker pull ${{ secrets.DOCKER_USERNAME }}/python-cicd:${{ github.sha }}&lt;/span&gt;
          &lt;span class="s"&gt;docker stop app || true&lt;/span&gt;
          &lt;span class="s"&gt;docker rm app || true&lt;/span&gt;
          &lt;span class="s"&gt;docker run -d --name app -p 3000:3000 ${{ secrets.DOCKER_USERNAME }}/python-cicd:${{ github.sha }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ Testing &amp;amp; Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;-v&lt;/code&gt; or &lt;code&gt;-vvv&lt;/code&gt; in SSH scripts for verbose logs&lt;/li&gt;
&lt;li&gt;Ensure the server user is in the docker group&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://github.com/nektos/act" rel="noopener noreferrer"&gt;&lt;code&gt;act&lt;/code&gt;&lt;/a&gt; to test GitHub Actions locally&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📈 Bonus: Make It Even Better
&lt;/h2&gt;

&lt;p&gt;Here’s how you can upgrade your deployment pipeline:&lt;/p&gt;

&lt;h3&gt;
  
  
  🔄 Add Rollback Logic
&lt;/h3&gt;

&lt;p&gt;If a deployment fails, you can roll back to a previous image by storing the old commit SHA or Docker tag and restarting the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; app &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 your-docker-user/python-cicd:previous-commit-sha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🌐 Blue-Green Deployments with Nginx
&lt;/h3&gt;

&lt;p&gt;Avoid downtime by running two versions of your app (e.g., app-blue and app-green) and switching traffic using Nginx.&lt;/p&gt;

&lt;p&gt;Example Nginx Config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upstream backend {
    server 127.0.0.1:3001;  # app-blue
    # server 127.0.0.1:3002;  # app-green
}

server {
    listen 80;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To switch versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy app-green on port 3002&lt;/li&gt;
&lt;li&gt;Update Nginx to point to 3002&lt;/li&gt;
&lt;li&gt;Reload Nginx: sudo systemctl reload nginx&lt;/li&gt;
&lt;li&gt;Remove old container if needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In your GitHub Action SSH script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d --name app-green -p 3002:3000 your-docker-user/python-cicd:${{ github.sha }}
sudo sed -i 's/3001/3002/' /etc/nginx/sites-available/default
sudo systemctl reload nginx
docker stop app-blue || true
docker rm app-blue || true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables &lt;strong&gt;zero-downtime deploys&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔔 Send Deployment Alerts to Slack
&lt;/h3&gt;

&lt;p&gt;Keep your team updated automatically using a Slack webhook.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;a href="https://api.slack.com/messaging/webhooks" rel="noopener noreferrer"&gt;Slack Incoming Webhook&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;SLACK_WEBHOOK&lt;/code&gt; as a GitHub Secret&lt;/li&gt;
&lt;li&gt;Use this step in your GitHub Actions:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Notify Slack
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    fields: repo,message,commit,author
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll get instant alerts on success, failure, or cancellation of deployments.&lt;/p&gt;




&lt;h2&gt;
  
  
  💬 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Automating deployments with GitHub Actions and SSH is a game-changer for solo developers and teams alike. Once configured, every git push to your main branch becomes a full-cycle delivery—from source code to a live service.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>devops</category>
      <category>docker</category>
      <category>cicd</category>
    </item>
    <item>
      <title>CI/CD Pipeline with GitHub Actions: From Code to Deployment</title>
      <dc:creator>Amol Mali</dc:creator>
      <pubDate>Sun, 29 Jun 2025 06:03:34 +0000</pubDate>
      <link>https://forem.com/devopswithamol/cicd-pipeline-with-github-actions-from-code-to-deployment-3e65</link>
      <guid>https://forem.com/devopswithamol/cicd-pipeline-with-github-actions-from-code-to-deployment-3e65</guid>
      <description>&lt;p&gt;Modern software development demands speed, consistency, and automation. CI/CD (Continuous Integration and Continuous Deployment) is a crucial practice that automates the steps between writing code and deploying it to production.&lt;/p&gt;

&lt;p&gt;In this article, we’ll walk through building a complete CI/CD pipeline using GitHub Actions — from committing code to deploying a containerized Python app.&lt;/p&gt;

&lt;p&gt;Whether you’re a developer, DevOps engineer, or curious learner, this guide will help you go from code to deployment with confidence.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 What is GitHub Actions?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt; is a built-in CI/CD and automation platform in GitHub. It lets you trigger workflows based on events — like pushing code, creating pull requests, or scheduling jobs — and run scripts to build, test, or deploy applications.&lt;/p&gt;

&lt;p&gt;✅ No external CI/CD server needed&lt;br&gt;
✅ Fully integrated with your repository&lt;br&gt;
✅ Free for public repos and generous free tier for private ones&lt;/p&gt;


&lt;h2&gt;
  
  
  🧰 Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving in, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;Docker Hub account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A Docker personal access token for authentication
→ &lt;a href="https://docs.docker.com/security/for-developers/access-tokens/#create-an-access-token" rel="noopener noreferrer"&gt;How to create one&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker installed locally (for optional manual testing)
Basic knowledge of Python and Git&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  📦 What We’ll Build
&lt;/h2&gt;

&lt;p&gt;We’ll set up a pipeline that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Triggers on every push to the main branch&lt;/li&gt;
&lt;li&gt;Installs dependencies&lt;/li&gt;
&lt;li&gt;Builds a Docker image&lt;/li&gt;
&lt;li&gt;Pushes the image to Docker Hub&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This entire process will be automated via GitHub Actions.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔗 GitHub Repo
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/amolxops/python-cicd-github-actions" rel="noopener noreferrer"&gt;github.com/amolxops/python-cicd-github-actions.git&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🐍 The Python App
&lt;/h2&gt;

&lt;p&gt;We’ll use a simple Flask application that returns “Hello, Welcome to the World of Github Actions CI/CD workflow!” when accessed. You can adapt this to any app stack you prefer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;app.py&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello, Welcome to the World of Github Actions CI/CD workflow!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=3000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;requirements.txt&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flask==2.3.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dockerfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 3000

CMD ["python", "app.py"]
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 3000

CMD ["python", "app.py"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔧 Setting Up GitHub Actions Workflow
&lt;/h2&gt;

&lt;p&gt;Create a workflow file at &lt;strong&gt;.github/workflows/ci-cd.yml&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: CI/CD Pipeline

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: |
          pip install -r requirements.txt

  docker:
    needs: build
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Log in to Docker Hub
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

      - name: Build and Push Docker Image
        run: |
          docker build -t ${{ secrets.DOCKER_USERNAME }}/python-cicd:${{ github.sha }} .
          docker push ${{ secrets.DOCKER_USERNAME }}/python-cicd:${{ github.sha }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔐 Adding GitHub Secrets
&lt;/h2&gt;

&lt;p&gt;Keep credentials safe by adding secrets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add: &lt;code&gt;DOCKER_USERNAME&lt;/code&gt; and &lt;code&gt;DOCKER_PASSWORD&lt;/code&gt;
These allow the workflow to authenticate securely with Docker Hub.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ✅ What Happens Now?
&lt;/h2&gt;

&lt;p&gt;With this setup, every time you push to main branch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitHub checks out the latest code&lt;/li&gt;
&lt;li&gt;Installs dependencies&lt;/li&gt;
&lt;li&gt;Builds a Docker image&lt;/li&gt;
&lt;li&gt;Pushes it to your Docker Hub (tagged with the commit SHA)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;✨ No more manual Docker builds or pushes!&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Bonus: Running the Container
&lt;/h2&gt;

&lt;p&gt;After the image is pushed, run it anywhere Docker is installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -p 3000:3000 your-username/python-cicd:&amp;lt;commit-sha&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to deploy it to a server automatically? Add a new job using &lt;code&gt;appleboy/ssh-action&lt;/code&gt; to deploy via SSH. Let me know if you'd like help extending the workflow!&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Wrap-Up
&lt;/h2&gt;

&lt;p&gt;CI/CD pipelines make development faster and more reliable. Using &lt;strong&gt;GitHub Actions&lt;/strong&gt;, you’ve automated everything from building a Docker image to pushing it to the registry.&lt;/p&gt;

&lt;p&gt;This guide covered:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A simple Python + Flask app&lt;/li&gt;
&lt;li&gt;Dockerizing the app&lt;/li&gt;
&lt;li&gt;Automating with GitHub Actions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Whether you’re building APIs, websites, or internal tools — this foundation sets you up for scalable and efficient deployments.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙌 Let’s Connect
&lt;/h2&gt;

&lt;p&gt;If you found this guide helpful or have questions, drop a comment or connect with me here on Medium. Follow for more content on DevOps, automation, and Linux tips.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What is DevOps? A Practical Guide for Beginners</title>
      <dc:creator>Amol Mali</dc:creator>
      <pubDate>Thu, 26 Jun 2025 11:42:48 +0000</pubDate>
      <link>https://forem.com/devopswithamol/what-is-devops-a-practical-guide-for-beginners-ch9</link>
      <guid>https://forem.com/devopswithamol/what-is-devops-a-practical-guide-for-beginners-ch9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;“DevOps isn’t a role. It’s a mindset — and a toolchain.”&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In today’s fast-paced digital world, businesses are under pressure to deliver high-quality software faster than ever before. Traditional software development and IT operations often function in silos, leading to slow deployments, miscommunication, and unreliable systems. That’s where DevOps comes in.&lt;/p&gt;

&lt;p&gt;DevOps is more than just a set of tools — it’s a cultural and professional movement that emphasizes collaboration, communication, and integration between software developers and IT operations. This guide is your practical starting point for understanding DevOps and how to begin your journey toward adopting it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 What is DevOps?
&lt;/h2&gt;

&lt;p&gt;DevOps (a combination of “Development” and “Operations”) is a methodology that aims to unify software development (Dev) and IT operations (Ops). The goal is to shorten the software development lifecycle and deliver high-quality software continuously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Principles of DevOps:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration:&lt;/strong&gt; Breaking down silos between dev and ops teams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation:&lt;/strong&gt; Streamlining repetitive tasks like testing, integration, and deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous Integration/Continuous Deployment (CI/CD):&lt;/strong&gt; Delivering code changes quickly and reliably.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring &amp;amp; Feedback:&lt;/strong&gt; Continuously tracking performance and using insights to improve.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 Why DevOps Matters: Key Benefits
&lt;/h2&gt;

&lt;p&gt;Adopting DevOps transforms how teams build and deliver software. Here’s how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster Deployment Cycles:&lt;/strong&gt; Teams release updates more frequently and with greater confidence.
&lt;em&gt;Example: Amazon deploys code every 11.7 seconds on average — thanks to robust DevOps practices.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved Collaboration:&lt;/strong&gt; DevOps breaks down walls between departments, fostering a culture of shared responsibility and faster issue resolution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Higher Quality Releases:&lt;/strong&gt;
With automated testing and continuous monitoring, teams can catch and fix bugs earlier in the lifecycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster Time to Market:&lt;/strong&gt; Organizations can respond to market demands swiftly, providing a competitive edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More Reliable Systems:&lt;/strong&gt; With infrastructure-as-code and real-time monitoring, environments are more consistent and predictable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Savings:&lt;/strong&gt; DevOps enables more efficient resource allocation and cost optimization.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛠️ Getting Started with DevOps: A Beginner’s Guide
&lt;/h2&gt;

&lt;p&gt;Here’s a step-by-step roadmap for anyone new to DevOps:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Understand the DevOps Culture
&lt;/h3&gt;

&lt;p&gt;DevOps isn’t just about tools — it starts with a mindset shift:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Embrace collaboration:&lt;/strong&gt; Everyone owns the product’s success, from developers to operations to QA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail fast, learn faster:&lt;/strong&gt; Mistakes are learning opportunities. Automate and iterate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer focus:&lt;/strong&gt; Continuously gather feedback and improve based on real-world usage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;📌 Tip: Encourage blameless postmortems and daily standups to foster transparency.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Learn Essential DevOps Tools
&lt;/h3&gt;

&lt;p&gt;While culture is key, tools enable the practices. Here are some foundational ones:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Tool — Purpose&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Git:&lt;/strong&gt; Version control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jenkins / GitHub Actions:&lt;/strong&gt; Automate build, test, and deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker:&lt;/strong&gt; Containerization (lightweight environments)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes:&lt;/strong&gt; Container orchestration at scale&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform:&lt;/strong&gt; Infrastructure as Code (IaC)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus + Grafana:&lt;/strong&gt; Monitoring and visualization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ELK Stack:&lt;/strong&gt; Log aggregation and analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with one tool at a time — e.g., Jenkins for CI/CD or Docker for local development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Build a CI/CD Pipeline
&lt;/h3&gt;

&lt;p&gt;A CI/CD pipeline automates the process of taking code from development to production.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Continuous Integration (CI):&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Developers push code to a shared repository (e.g., GitHub).&lt;/li&gt;
&lt;li&gt;Automated tests run to validate the code.&lt;/li&gt;
&lt;li&gt;Builds are triggered automatically.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Continuous Deployment (CD):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Code is automatically deployed to staging or production if all checks pass.&lt;/li&gt;
&lt;li&gt;Rollbacks can be automated in case of failures.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  &lt;em&gt;📌 Best Practices:&lt;/em&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Keep builds fast and atomic.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Run unit + integration tests.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Use feature toggles for safe deployments.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Implement Monitoring and Feedback Loops
&lt;/h3&gt;

&lt;p&gt;Once code is live, the job isn’t done.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring:&lt;/strong&gt; Use tools like Prometheus, Datadog, or New Relic to monitor application performance and infrastructure health.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging:&lt;/strong&gt; Implement centralized logging with tools like ELK Stack or Fluentd.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerting:&lt;/strong&gt; Set up real-time alerts for anomalies or failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;📌 Why it matters: Monitoring lets teams detect issues before users do and continuously improve based on performance and usage patterns.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Overcoming Common DevOps Challenges
&lt;/h2&gt;

&lt;p&gt;Transitioning to DevOps can be tough. Here are common pitfalls and how to tackle them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resistance to change:&lt;/strong&gt; Encourage small wins and involve all stakeholders early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool overload:&lt;/strong&gt; Avoid chasing trends — adopt tools that solve your specific needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of visibility:&lt;/strong&gt; Make pipelines and logs transparent and accessible to all teams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Siloed knowledge:&lt;/strong&gt; Promote cross-functional training and documentation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 How to Get Started in DevOps
&lt;/h2&gt;

&lt;p&gt;If you’re new, here’s a simple roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learn &lt;strong&gt;Linux&lt;/strong&gt; and &lt;strong&gt;Networking Basics&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Understand &lt;strong&gt;Git&lt;/strong&gt; and &lt;strong&gt;CI/CD tools&lt;/strong&gt; (start with GitHub Actions)&lt;/li&gt;
&lt;li&gt;Get hands-on with &lt;strong&gt;Docker&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Explore &lt;strong&gt;IaC&lt;/strong&gt; with &lt;strong&gt;Terraform&lt;/strong&gt; or &lt;strong&gt;Ansible&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;strong&gt;Kubernetes&lt;/strong&gt; locally (&lt;strong&gt;Minikube&lt;/strong&gt; or &lt;strong&gt;Kind&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Monitor with &lt;strong&gt;Prometheus + Grafana&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Learn Cloud Basics (Start with AWS Free Tier or Google Cloud Free)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📚 Resources I Recommend
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://roadmap.sh/devops" rel="noopener noreferrer"&gt;Roadmap.sh - DevOps Roadmap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitpod.io/#https://github.com/docker-library/hello-world" rel="noopener noreferrer"&gt;Docker Playground on Gitpod&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://minikube.sigs.k8s.io/docs/" rel="noopener noreferrer"&gt;Minikube Setup Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@TechWorldwithNana" rel="noopener noreferrer"&gt;TechWorld with Nana YouTube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  👋 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;DevOps isn’t a destination — it’s an evolving journey. Whether you’re a developer curious about automation or an ops engineer eager to collaborate better, embracing DevOps will elevate your software delivery process and product quality.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Start small. Learn continuously. Automate wisely.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>beginners</category>
      <category>devops</category>
      <category>cicd</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
