Devops

How to Build a CI/CD Pipeline for Ansible Using GitHub Actions

April 7, 2026
Published
#Ansible#Automation#CI/CD#DevOps#GitHub Actions#Infrastructure as Code

Shipping infrastructure changes without validation is a fast way to break environments. If you’re using Ansible, you already treat infrastructure as code—so it deserves the same CI/CD rigor as application code.

Let’s build a practical CI/CD pipeline for Ansible using GitHub Actions that checks syntax, enforces best practices, and optionally deploys changes safely.

What we’re aiming for

A solid Ansible CI/CD pipeline should:

  • Validate YAML syntax
  • Run ansible-lint for best practices
  • Check playbook execution in a safe environment
  • Optionally deploy to target servers

We’ll wire all of this into a GitHub Actions workflow.

Repository layout (example)

Your project might look something like this:

TEXT
1
2.
3├── inventory/
4│   └── hosts.yml
5├── playbooks/
6│   └── site.yml
7├── roles/
8├── group_vars/
9├── host_vars/
10└── .github/
11    └── workflows/
12        └── ansible-ci.yml
13

Start with the workflow file

Create .github/workflows/ansible-ci.yml. Here’s a working pipeline you can adapt:

YAML
1
2name: Ansible CI/CD Pipeline
3
4on:
5  push:
6    branches: [ "main" ]
7  pull_request:
8    branches: [ "main" ]
9
10jobs:
11  ansible-ci:
12    runs-on: ubuntu-latest
13
14    steps:
15      - name: Checkout repository
16        uses: actions/checkout@v4
17
18      - name: Set up Python
19        uses: actions/setup-python@v5
20        with:
21          python-version: '3.11'
22
23      - name: Install dependencies
24        run: |
25          pip install ansible ansible-lint
26
27      - name: Validate YAML syntax
28        run: |
29          ansible-playbook playbooks/site.yml --syntax-check
30
31      - name: Run ansible-lint
32        run: |
33          ansible-lint playbooks/site.yml
34

This already gives you a basic CI pipeline for Ansible with GitHub Actions. Every push or pull request will be validated.

Here’s where things get interesting: testing execution

Syntax checks don’t guarantee your playbook works. To go further, you can simulate execution using a local inventory or a containerized environment.

Option: Run against localhost

YAML
1
2- name: Run playbook locally
3  run: |
4    ansible-playbook -i "localhost," -c local playbooks/site.yml
5

This is useful for roles that don’t depend on external infrastructure.

Option: Use Molecule (advanced)

If you want proper testing, Molecule lets you spin up test instances (Docker, Vagrant, etc.). That’s a deeper setup but worth it for production-grade pipelines.

Adding deployment to the pipeline

Once validation passes, you may want to deploy automatically. This is where many teams get into trouble—so keep it controlled.

Use GitHub secrets for SSH

Store sensitive data securely:

  • SSH_PRIVATE_KEY
  • ANSIBLE_HOST

Deployment step example

YAML
1
2- name: Setup SSH
3  run: |
4    mkdir -p ~/.ssh
5    echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
6    chmod 600 ~/.ssh/id_rsa
7
8- name: Run Ansible playbook on remote host
9  run: |
10    ansible-playbook -i inventory/hosts.yml playbooks/site.yml
11

A common mistake developers make is deploying on every push. Instead, restrict deployment to specific conditions.

Deploy only on main branch

TEXT
1
2if: github.ref == 'refs/heads/main'
3

Adding environments for safer releases

GitHub Actions supports environments like staging and production. You can require approvals before deployment.

Example:

YAML
1
2environment:
3  name: production
4

This adds a manual checkpoint before your Ansible deployment runs.

Speeding things up

CI pipelines can get slow, especially when installing dependencies repeatedly.

Cache Python packages

YAML
1
2- name: Cache pip dependencies
3  uses: actions/cache@v4
4  with:
5    path: ~/.cache/pip
6    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
7

This can significantly reduce pipeline runtime.

Common pitfalls (worth avoiding)

  • Skipping linting: ansible-lint catches subtle issues early
  • Hardcoding secrets: always use GitHub Secrets
  • No dry runs: use --check mode when possible
  • Blind deployments: add environment approvals or conditions

A slightly more complete pipeline

Here’s a refined version combining everything:

YAML
1
2name: Ansible CI/CD
3
4on:
5  push:
6    branches: [ "main" ]
7
8jobs:
9  deploy:
10    runs-on: ubuntu-latest
11
12    steps:
13      - uses: actions/checkout@v4
14
15      - uses: actions/setup-python@v5
16        with:
17          python-version: '3.11'
18
19      - run: pip install ansible ansible-lint
20
21      - run: ansible-playbook playbooks/site.yml --syntax-check
22
23      - run: ansible-lint playbooks/site.yml
24
25      - name: Dry run
26        run: |
27          ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check
28
29      - name: Deploy
30        if: github.ref == 'refs/heads/main'
31        run: |
32          ansible-playbook -i inventory/hosts.yml playbooks/site.yml
33

Why this setup works well

You get fast feedback on every change, consistent validation across teams, and safer deployments. More importantly, your infrastructure changes become predictable.

And that’s the real win: fewer surprises in production.

Where to go next

If you want to push this further:

  • Integrate Molecule for role testing
  • Add Slack or email notifications
  • Use dynamic inventories (AWS, Azure, etc.)
  • Split workflows for CI and CD stages

Once this pipeline is in place, your Ansible workflows stop being “scripts you hope work” and become a reliable, repeatable delivery system.

Comments

Leave a comment on this article with your name, email, and message.

Loading comments...

Similar Articles

More posts from the same category you may want to read next.

Share: