Configuration Management with Ansible - Pilot

Published: (January 30, 2026 at 08:31 AM EST)
6 min read
Source: Dev.to

Source: Dev.to

12ww1160

Introduction

Welcome to the first installment of our Ansible series! Whether you’re refreshing your skills or diving into Ansible for the first time, this post will give you a solid foundation. Ansible is a powerful, agentless automation tool for configuration management, application deployment, and orchestration. It’s simple yet scalable, making it ideal for managing everything from a homelab to enterprise infrastructures.

In this opener, we’ll focus on how to structure your Ansible repositories effectively—debating the merits of a single repo versus separating variables and secrets. We’ll also walk through a real‑world scenario for handling diverse hosts, emphasize security (because automation without safeguards is a recipe for disaster), and visualize a typical workflow with a Mermaid diagram. By the end, you’ll have a blueprint to start your own projects securely and efficiently.

Future posts will cover advanced topics like role development, testing with Molecule, integrating with cloud providers, and optimizing for large‑scale environments. Let’s jump in!

Choosing Your Repository Structure: Single Repo vs. Separate Vars/Secrets

When starting with Ansible, one of the first decisions is how to organize your code. You have two main approaches:

  1. Keep everything (playbooks, roles, inventories, variables, and even encrypted secrets) in one repository.
  2. Split variables and secrets into a separate repository.

Both are valid; your choice hinges on team size, security needs, and workflow complexity.

The Single Repository Approach

This is the go‑to for individuals, small teams, or when you’re just getting (re)started. Everything lives in one Git repo, making it easy to clone, develop, and run playbooks locally. A recommended structure looks like this:

ansible-project/
├── inventories/
│   ├── production/          # Host lists for prod env
│   └── staging/             # Separate for non‑prod
├── group_vars/
│   ├── all/
│   │   ├── vars.yml         # Non‑sensitive defaults
│   │   └── secrets.yml      # Vault‑encrypted sensitive data
│   └── webservers/          # Group‑specific vars
├── host_vars/               # Per‑host overrides
├── playbooks/               # Your main YAML playbooks
├── roles/                   # Reusable roles (e.g., common, nginx)
├── ansible.cfg              # Config overrides
└── requirements.yml         # Galaxy collections/roles

Pros

  • Simplicity: Clone once, and you’re ready to run ansible-playbook site.yml.
  • Focus on learning: Spend time on Ansible concepts (variable precedence, facts, handlers) instead of managing multiple repos.
  • Built‑in security: Use Ansible Vault to encrypt sensitive files or individual variables (!vault | inline). Vault uses AES‑256 encryption, which is robust for most use cases.

Cons

  • Potential for over‑exposure: If your repo is shared, everyone with access can see the structure of secrets (even if encrypted). In larger teams, this might violate least‑privilege principles.

The Separate Vars/Secrets Repository Approach

As your setup grows—think multiple teams, strict compliance (e.g., PCI‑DSS, HIPAA), or environments with siloed access—move sensitive variables and inventories to a dedicated repo. Non‑sensitive playbooks and roles stay in the main repo.

ansible-playbooks/   (public‑ish: playbooks, roles)
ansible-secrets/     (restricted: vars, inventories, Vault files)

Pros

  • Enhanced security: Limit access to the secrets repo. Developers can contribute to playbooks without ever touching production credentials.
  • Better for environments: One secrets repo per environment (or branches with protections) reduces mix‑ups.
  • Compliance‑friendly: Auditors love separation of code (logic) from data (configs/secrets).

Cons

  • Added complexity: You’ll need to clone both repos, use --extra-vars @../secrets/vars.yml, or integrate via Git submodules/symlinks. This can slow onboarding and local testing.

My Recommendation

If you’re refreshing skills or working solo/small team, start with a single repo. It’s simpler and lets you iterate quickly. Encrypt only what’s truly sensitive—don’t Vault everything, as it complicates diffs and reviews. Use multiple Vault passwords or IDs for different environments, e.g.:

ansible-vault encrypt --vault-id prod@prompt prod_secrets.yml

When pain points emerge (e.g., too many people accessing prod secrets), migrate secrets to a separate repo or—better yet—dynamic sources like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault. Pull secrets at runtime with lookup plugins to avoid Git entirely. This hybrid is a 2026 best practice for zero‑trust environments.

Remember: Security isn’t just about repository structure—it’s about the Vault password. Never commit it! Store it in a password manager, inject via CI/CD secrets, or prompt interactively. Regularly rotate passwords and audit access.

Scenario: Managing Diverse Hosts with a Single Config Run

Let’s make this concrete. Imagine managing a fleet of 100+ servers:

  • Web servers in AWS
  • Databases in Azure
  • Edge devices on‑prem

Configurations vary—prod webs need strict firewalls, staging allows debugging ports, and databases have unique credentials.

With Ansible, handle this in one regular playbook run by leveraging inventories and variables.

Inventory Setup

Use dynamic or static inventories grouped logically.

[webservers]
web1 ansible_host=10.0.1.10
web2 ansible_host=10.0.1.11

[databases]
db1 ansible_host=10.1.2.20
db2 ansible_host=10.1.2.21

[edge_devices]
edge1 ansible_host=192.168.0.5
edge2 ansible_host=192.168.0.6

Playbook Example

---
- name: Configure all hosts
  hosts: all
  become: true
  vars_files:
    - "{{ playbook_dir }}/../group_vars/all/vars.yml"
    - "{{ playbook_dir }}/../group_vars/all/secrets.yml"   # encrypted
  tasks:
    - name: Ensure common packages are installed
      apt:
        name: "{{ common_packages }}"
        state: present
      when: ansible_os_family == 'Debian'

    - name: Deploy web server configuration
      include_role:
        name: nginx
      when: "'webservers' in group_names"

    - name: Deploy database configuration
      include_role:
        name: postgresql
      when: "'databases' in group_names"

    - name: Configure edge device firewall
      include_role:
        name: edge_firewall
      when: "'edge_devices' in group_names"

Workflow Diagram (Mermaid)

flowchart TD
    A[Clone Repo] --> B[Load Inventory]
    B --> C[Decrypt Vault Files]
    C --> D[Run Playbook]
    D --> E{Host Group?}
    E -->|webservers| F[Apply nginx Role]
    E -->|databases| G[Apply postgresql Role]
    E -->|edge_devices| H[Apply edge_firewall Role]
    F & G & H --> I[Report Results]

Additional inventory snippet:

0.0.1 ansible_host=10.0.0.1 env=prod
web2 ansible_host=10.0.0.2 env=staging

[databases]
db1 ansible_host=192.168.1.1 env=prod db_type=postgres

TL;DR

  • Start simple: One repo with Vault‑encrypted secrets.
  • Scale securely: Split secrets into a dedicated repo or external secret manager when needed.
  • Never store Vault passwords in Git. Rotate them regularly.
  • Leverage inventories & group variables to manage heterogeneous environments from a single playbook run.

Variable Hierarchy

Ansible’s precedence loads vars intelligently.

  • group_vars/all/vars.yml – Global defaults (e.g., ntp_server: "pool.ntp.org").
  • group_vars/webservers/vars.yml – Group‑specific (e.g., http_port: 80).
  • host_vars/web1.yml – Host overrides (e.g., firewall_rules: strict).
  • Encryptedgroup_vars/all/secrets.yml for API keys, DB passwords.

Playbook Execution

A single site.yml applies configs conditionally.

---
- name: Configure all hosts
  hosts: all
  become: true
  roles:
    - common   # Installs base packages, sets NTP
  tasks:
    - name: Set up web server
      when: "'webservers' in group_names"
      include_role:
        name: nginx
      vars:
        nginx_port: "{{ http_port }}"

    - name: Secure database
      when: "'databases' in group_names"
      include_role:
        name: postgres
      vars:
        db_pass: "{{ vault_db_password }}"   # From encrypted vars

Run it regularly via cron or CI/CD:

ansible-playbook -i inventories/production site.yml --vault-id prod@password_file

This ensures consistency while respecting differences—prod gets locked down, staging stays flexible.

Security Stress Point

In this multi‑host setup, Vault is crucial. Exposing plaintext passwords could compromise your entire fleet.

  • Always use --diff to review changes.
  • Limit become privileges.
  • Enable Ansible’s no_log for sensitive tasks to avoid logging secrets.

Workflow Visualization: From Development to CI/CD Execution

Below is a Mermaid diagram that shows a secure Ansible workflow. Playbooks are developed in Git, pushed, and executed via CI/CD tools (Jenkins, GitLab CI, GitHub Actions, Rundeck, etc.). Security gates (approvals, secret injection) are highlighted.

Mermaid diagram

The flow emphasizes security: secrets never hit Git plaintext, CI/CD handles injection, and approvals prevent unauthorized deploys. In GitHub Actions, for example, use secrets.VAULT_PASSWORD in your workflow YAML.

Wrapping Up: Secure Automation Starts Here

Structuring your Ansible repo thoughtfully—starting simple and scaling securely—sets you up for success.

  • Prioritize Vault for secrets.
  • Leverage inventories/vars for flexibility.
  • Integrate CI/CD for reliable execution.

This approach minimizes risk while maximizing efficiency, especially across diverse environments.

In the next post, we’ll dive into writing your first roles and testing them with Molecule.

Feedback & Support

  • Questions or tips? Drop them in the comments or on our feedback portal.
  • Found this post helpful? You can support me:

Buy Me a Coffee

  • Hetzner Referral
    Hetzner Referral

  • ConfDroid Feedback Portal
    ConfDroid Feedback Portal

Back to Blog

Related posts

Read more »

30.Delete EC2 Instance Using Terraform

markdown !Cover image for 30.Delete EC2 Instance Using Terraformhttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/htt...