Ansible playbooks for configuration management

Ryan Nakamura Feb 2026
2 tabs
---
# Main playbook
- name: Configure web servers
  hosts: webservers
  become: yes
  vars:
    app_name: web-app
    app_user: deploy
    app_dir: /opt/web-app
    node_version: "20"
    nginx_worker_processes: auto

  pre_tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600

  roles:
    - common
    - nodejs
    - nginx
    - app

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

    - name: restart app
      systemd:
        name: "{{ app_name }}"
        state: restarted
        daemon_reload: yes

  tasks:
    # System packages
    - name: Install system packages
      apt:
        name:
          - build-essential
          - git
          - curl
          - htop
          - unzip
        state: present
      tags: [packages]

    # Create app user
    - name: Create application user
      user:
        name: "{{ app_user }}"
        system: yes
        shell: /bin/bash
        home: "{{ app_dir }}"
        createhome: yes

    # App directory
    - name: Create app directories
      file:
        path: "{{ item }}"
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
        mode: "0755"
      loop:
        - "{{ app_dir }}"
        - "{{ app_dir }}/releases"
        - "{{ app_dir }}/shared"
        - "{{ app_dir }}/shared/logs"
        - /var/log/{{ app_name }}

    # Deploy app
    - name: Clone application repository
      git:
        repo: "https://github.com/example/{{ app_name }}.git"
        dest: "{{ app_dir }}/releases/{{ app_version }}"
        version: "{{ app_version }}"
      become_user: "{{ app_user }}"
      tags: [deploy]

    # Install dependencies
    - name: Install npm dependencies
      npm:
        path: "{{ app_dir }}/releases/{{ app_version }}"
        production: yes
      become_user: "{{ app_user }}"
      tags: [deploy]

    # Symlink current release
    - name: Symlink current release
      file:
        src: "{{ app_dir }}/releases/{{ app_version }}"
        dest: "{{ app_dir }}/current"
        state: link
        owner: "{{ app_user }}"
      notify: restart app
      tags: [deploy]

    # App environment file
    - name: Create environment file
      template:
        src: templates/env.j2
        dest: "{{ app_dir }}/shared/.env"
        owner: "{{ app_user }}"
        mode: "0600"
      notify: restart app
      tags: [config]

    # Systemd service
    - name: Create systemd service
      template:
        src: templates/app.service.j2
        dest: /etc/systemd/system/{{ app_name }}.service
        mode: "0644"
      notify: restart app
      tags: [service]

    # Nginx config
    - name: Configure nginx site
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/{{ app_name }}
        mode: "0644"
      notify: restart nginx
      tags: [nginx]

    - name: Enable nginx site
      file:
        src: /etc/nginx/sites-available/{{ app_name }}
        dest: /etc/nginx/sites-enabled/{{ app_name }}
        state: link
      notify: restart nginx
      tags: [nginx]

    # Enable and start services
    - name: Enable and start app
      systemd:
        name: "{{ app_name }}"
        enabled: yes
        state: started
        daemon_reload: yes
      tags: [service]

    # Firewall
    - name: Configure firewall
      ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - "22"
        - "80"
        - "443"
      tags: [security]

    - name: Enable firewall
      ufw:
        state: enabled
        policy: deny
      tags: [security]
2 files · yaml, ini Explain with highlit

Ansible automates server configuration and application deployment without agents. Playbooks are YAML files describing desired system state. hosts targets machines from the inventory. tasks execute modules like apt, copy, template, service, and user. handlers restart services only when notified of changes. roles organize related tasks, templates, and variables into reusable packages. Variables can be defined in group_vars, host_vars, or passed at runtime. when conditionals skip tasks based on facts or variables. with_items and loop iterate over lists. Jinja2 templates generate configuration files dynamically. become: yes escalates privileges with sudo. tags enable selective task execution. Idempotent tasks safely run repeatedly.