Automate Server Deployments with Ansible & GitOps: A Developer’s Guide


Why Automate Server Deployments?

In today’s fast-moving development cycles, manual server deployments are not just inefficient — they’re risky. Human error, inconsistent environments, and deployment downtime can cripple your CI/CD pipeline. That’s where Ansible and GitOps come in.

Ansible is an open-source configuration management and automation tool that lets you define infrastructure as code (IaC), and GitOps is a methodology that treats infrastructure and applications as code stored in version-controlled repositories — enabling declarative, auditable, and automated deployments.

Together, they form a powerhouse for automated, repeatable, and scalable server deployments — no more “I’ll just SSH into the server and run it again” workflows.


What You’ll Learn

By the end of this post, you’ll know:

  • How to set up Ansible for server automation.
  • How to integrate Ansible with GitOps for declarative deployments.
  • How to automate deployments using Git hooks and CI/CD.
  • How to manage server state, rollback, and auditing.
  • Best practices for production-grade automation.

Setting Up Ansible

Step 1: Install Ansible

First, install Ansible on your control machine (e.g., your local laptop or CI server):

# On Ubuntu/Debian
sudo apt update
sudo apt install ansible

# On macOS with Homebrew
brew install ansible

Verify installation:

ansible --version

Step 2: Create Your First Playbook

A playbook is Ansible’s core automation configuration file — written in YAML.

Create a simple playbook site.yml:

---
- hosts: webservers
  become: yes
  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present

    - name: Start Nginx
      service:
        name: nginx
        state: started
        enabled: yes

✅ Note: Replace webservers with your actual inventory group (more on that next).

Step 3: Define Your Inventory

Ansible uses an inventory file to know which servers to target. Create inventory.ini:

[webservers]
server1 ansible_host=192.168.1.10
server2 ansible_host=192.168.1.11

💡 Tip: Use dynamic inventory if managing dozens or hundreds of servers.

Step 4: Run Your Playbook

ansible-playbook site.yml -i inventory.ini

You’ll see Ansible connect to each server and execute tasks — no manual SSH required.


Introducing GitOps: The Declarative Way

GitOps is not just a buzzword — it’s a workflow. It says: “The system should always be in the desired state defined by code in Git.”

GitOps Principles

  1. Infrastructure as Code (IaC) — All server configurations live in Git.
  2. Declarative — You declare what you want, not how to get it.
  3. Automated Sync — Any change to Git triggers a sync to the server.
  4. Auditable — Every change is versioned, reviewed, and rollable.

How GitOps Fits with Ansible

Ansible is perfect for GitOps:

  • Playbooks = Configuration code
  • Git = State definition
  • CI/CD = Trigger sync

Integrating Ansible with GitOps

Step 1: Store Playbooks in Git

Create a repo for your infrastructure:

mkdir ansible-gitops && cd ansible-gitops
git init
git remote add origin https://github.com/yourname/ansible-gitops.git

Add your playbooks and inventory:

mkdir playbooks inventory
cp site.yml playbooks/
cp inventory.ini inventory/

Commit:

git add .
git commit -m "Initial Ansible playbooks"
git push origin main

Step 2: Use a CI/CD Tool to Trigger Deployments

Popular choices: GitHub Actions, GitLab CI, CircleCI, or ArgoCD (for more advanced GitOps).

Here’s a simple GitHub Actions workflow:

# .github/workflows/deploy.yml
name: Deploy Servers

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Install Ansible
        run: |
          sudo apt update && sudo apt install ansible -y

      - name: Deploy with Ansible
        run: |
          ansible-playbook playbooks/site.yml -i inventory/inventory.ini --become

🚀 This workflow runs every time main is pushed — automatically deploying your server configuration.

Step 3: Use Tags for Different Environments

Ansible supports tags, allowing you to run only specific tasks — ideal for staging, prod, etc.

Example: site.yml

---
- hosts: webservers
  become: yes
  tags: [nginx, firewall]
  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present

    - name: Start Nginx
      service:
        name: nginx
        state: started
        enabled: yes

Run only nginx tasks:

ansible-playbook site.yml --tags=nginx -i inventory.ini

Step 4: Handle Secrets Securely

Never store passwords or keys in Git! Use Ansible Vault:

ansible-vault create group_vars/common/vault.yml

In vault.yml:

ansible_user: myuser
ansible_password: mypassword

Use it in your playbook:

- name: Use vault vars
  ansible.builtin.lineinfile:
    path: /etc/ansible/credentials
    line: "{{ ansible_password }}"
    state: present

Run with vault:

ansible-playbook site.yml --vault-password-file ~/.vault_pass

Advanced GitOps with ArgoCD (Optional)

For more complex environments, consider ArgoCD — a powerful GitOps operator for Kubernetes. While this post focuses on servers, you can extend Ansible + GitOps to Kubernetes using ArgoCD.

ArgoCD polls your Git repo for changes and applies them via Ansible or Helm — automating infrastructure across layers.


Best Practices for Production Deployments

✅ Use Ansible Roles — Modularize your playbooks.

mkdir roles/nginx
cp site.yml roles/nginx/tasks/main.yml

✅ Idempotency — Ansible is idempotent — running the same playbook twice won’t break your system.

✅ Testing — Use ansible-test or molecule to test playbooks in a sandbox.

✅ Rollback — If something goes wrong, revert to the last known good state via Git tags or branches.

✅ Logging & Monitoring — Enable ansible-playbook --log-path and integrate with ELK stack or Grafana.

✅ Limit Permissions — Use SSH keys and restricted user roles — never run Ansible as root in production.

✅ Encrypt Secrets — Always use Ansible Vault for sensitive data.


Real-World Example: Deploying a Django App

Let’s say you want to deploy a Django app on Ubuntu servers.

Playbook: deploy_django.yml

---
- hosts: webapp_servers
  become: yes
  tasks:
    - name: Install Python 3.10
      apt:
        name: python3.10
        state: present

    - name: Install pip
      apt:
        name: python3-pip
        state: present

    - name: Install system dependencies
      apt:
        name:
          - nginx
          - postgresql
          - python3-venv
        state: present

    - name: Create app user
      user:
        name: djangoapp
        state: present

    - name: Clone app repo
      git:
        repo: https://github.com/yourname/django-app.git
        dest: /home/djangoapp/app
        version: main

    - name: Create virtualenv
      command: python3.10 -m venv /home/djangoapp/app/venv

    - name: Install dependencies
      command: /home/djangoapp/app/venv/bin/pip3 install -r /home/djangoapp/app/requirements.txt

    - name: Configure Nginx
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/app.conf
      notify: Restart nginx

    - name: Enable Nginx site
      file:
        path: /etc/nginx/sites-enabled/app.conf
        state: link
        src: /etc/nginx/sites-available/app.conf

    - name: Restart nginx
      service:
        name: nginx
        state: restarted

📝 Use Jinja2 templates for dynamic configs, e.g., {{ app_host }}, {{ db_url }}.


Troubleshooting Common Issues

❌ “Connection refused”

  • Check SSH keys and firewall rules.
  • Use ansible inventory --list to verify inventory.
  • Test connection: ansible -m ping -i inventory.ini webservers

❌ “Module not found”

  • Check if module is available: ansible-doc -l | grep module_name
  • Install modules via pip if needed.

❌ “Permission denied”

  • Use become: yes and ensure user has sudo rights.
  • Use ansible --become-method=sudo --ask-become-pass

❌ “Playbook failed on some hosts”

  • Use --check to test without applying changes.
  • Use ansible-playbook --diff to see what will change.

Conclusion: Automate with Confidence

Automating server deployments with Ansible and GitOps isn’t just a productivity hack — it’s a best practice for modern DevOps.

You’ve learned:

  • How to write idempotent, reusable Ansible playbooks
  • How to store infrastructure in Git and automate sync
  • How to integrate with CI/CD and handle secrets securely
  • How to extend to complex environments with ArgoCD

Next time you deploy, you won’t need to SSH into servers — you’ll just push to Git and let automation handle the rest.

🧠 Pro Tip: Always keep your ansible.cfg file in .gitignore — avoid leaking credentials or configs.


Ready to Automate?

Start small — automate one server, then scale. Use Ansible and GitOps together to move from “I deploy manually” to “I deploy automatically — and confidently.”

🚀 Your infrastructure is code. Your deployments are versioned. Your ops are automated.


Resources


Stay Connected

Follow for more DevOps deep dives, infrastructure automation, and CI/CD workflows.

— Yash