Rough draft notes and cheatsheet on Ansible. I’ve been using Ansible, a devop automation/configuration management tool, to handle the provisioning of the backend servers. It is a good alternative to Chef-Zero (Chef-Solo), and Ansible seems to be a lot easier to work with.


Simple usage

Test using ping

ansible <Server_Group_Name> -i <host_file> -m ping

ansible testserver -i my_hosts.txt -m ping

# for more detail (verbose)
ansible testserver -i hosts -m ping -vvvv
  • host_file must exist first. It defines server group names

my_hosts.txt (Ansible - Up and Running)

testserver ansible_ssh_host= ansible_ssh_port=2222 \
ansible_ssh_user=vagrant \

All this is not needed. Instead, use ansible.cfg

my_hosts.txt becomes

testserver ansible_ssh_host= ansible_ssh_port=2222 


ansible testserver -m ping  # no need for `-i host_file`

longer command. -m is not needed, since it is default -a: argument. By default (or -m), it is the actual command to run

ansible testserver -a "tail /var/log/dmesg"

sudo command (use -s)

ansible testserver -s -a "tail /var/log/syslog"

ansible testserver -s -m apt -a name=nginx

see ad-hoc commands below

view current state

ansible all -m setup


Run scripts and is recommended way of running plays

ansible-playbook <MY_PLAYBOOK>.yml 

ansible-playbook <MY_PLAYBOOK>.yml -f 10

ansible-playbook <MY_PLAYBOOK>.yml  --verbose

# hosts in this file instead of 
ansible-playbook -i <hosts> <MY_PLAYBOOK>.yml

Before running, see what hosts will run

ansible-playbook playbook.yml --list-hosts

Dry run

ansible-playbook foo.yml --check

Run scripts from client/nodes via pull

ansible-pull ....

Example (simple ping)

- hosts: vagrant
    - name: test connection

Example from doc

- hosts: webservers
  remote_user: root

      http_port: 80
      max_clients: 200
  remote_user: root
  - name: ensure apache is at the latest version
      yum: name=httpd state=latest
  - name: write the apache config file
      template: src=/srv/httpd.j2 dest=/etc/httpd.conf
      - restart apache
  - name: ensure apache is running (and enable it at boot)
      service: name=httpd state=started enabled=yes
      - name: restart apache
      service: name=httpd state=restarted


- hosts: all
    - name: ensure ntpd is at the latest version
      yum: pkg=ntp state=latest
      - restart ntpd
    - name: restart ntpd
      service: name=ntpd state=restarted


  1. hosts
  2. tasks
  3. handlers


- hosts: <names of machines that it should run on>
- hosts: all  # 'special var'?, runs on all machines

Multiple hosts can be defined in a single yml

- hosts: webservers
- hosts: databases

remote_user: <username to run as>
remote_user: root

In vagrant, use “vagrant” as the user and set sudo: true

remote_user: vagrant
sudo: true


service (run something)

- name: make sure apache is running
    service: name=httpd state=running

- name: Ensure nginx is running
    service: >

enabled=yes : allow service to start when system starts/reboots


more secure than shell, but cannot use <,>, |, & operation

- name: disable selinux
    command: /sbin/setenforce 0


run command in shell context (sim to command)

- name: run this command and ignore the result
    shell: /usr/bin/somecommand || /bin/true


- name: run this command and ignore the result
    shell: /usr/bin/somecommand
    ignore_errors: True

break into multiple line

- name: Copy ansible inventory file to client
    copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
            owner=root group=root mode=0644

Variables in template

- name: create a virtual host file for {{ vhost }}
    template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }}

apt (debian/ubuntu) Ansible doc

Add apt repository

- name: Ensure the passenger apt repository is added
    apt_repository: >
    repo='deb raring main'

apt example

# Install the package "foo"
- apt: name=foo state=present

Install multiple packages

- name: Install list of packages
  apt: pkg={{item}} state=present update_cache=yes cache_valid_time=3600
        - package1
        - package2
        - package3
  • cache_valid_time=3600: 1 hour or 3600 seconds, before doing apt-get update
  • update_cache=yes: do apt-get update first

    # Run the equivalent of "apt-get update" as a separate step
    - name: apt-get update
    apt: update_cache=yes
    # Update repositories cache and install "foo" package
    - apt: name=foo update_cache=yes
    - name: Ensure nginx is installed (using multiple lines)
        apt: >
    - name: Ensure the PGP key is installed
        apt_key: >


# Example that prints the loopback address and gateway for each host
- debug: msg="System {{ inventory_hostname }} has uuid {{ ansible_product_uuid }}"

- debug: var=myVar

Example (from Ansible - Up and Running):

hosts: server1
    - name: capture output of id command
      command: id -un
      register: login
    - debug: var=login  

using sed

  • recommends using lineinfile module instead

command: /bin/sed -r -i “s/^local[[:space:]]+all[[:space:]]+all[[:space:]]+peer/local all all md5/” /etc/postgresql/9.4/main/pg_hba.conf


  • instead of sed,

    lineinfile: >
        dest=<file name to read/write>
        line=<string to write/replace>
        regexp=<search line> # uses python regex
        state=present # (default) write
        #    =absent: delete line

Example (from doc)

- lineinfile: dest=/etc/selinux/config regexp=^SELINUX= line=SELINUX=enforcing
- lineinfile: dest=/etc/sudoers state=absent regexp="^%wheel"
- lineinfile: dest=/etc/hosts regexp='^127\.0\.0\.1' line=' localhost' owner=root group=root mode=0644
- lineinfile: dest=/etc/httpd/conf/httpd.conf regexp="^Listen " insertafter="^#Listen " line="Listen 8080"
- lineinfile: dest=/etc/services regexp="^# port for http" insertbefore="^www.*80/tcp" line="# port for http by default"

# Add a line to a file if it does not exist, without passing regexp
- lineinfile: dest=/tmp/testfile line=" foo"

# Fully quoted because of the ': ' on the line. See the Gotchas in the YAML docs.
- lineinfile: "dest=/etc/sudoers state=present regexp='^%wheel' line='%wheel ALL=(ALL) NOPASSWD: ALL'"

- lineinfile: dest=/opt/jboss-as/bin/standalone.conf regexp='^(.*)Xms(\d+)m(.*)$' line='\1Xms${xms}m\3' backrefs=yes

# Validate the sudoers file before saving
- lineinfile: dest=/etc/sudoers state=present regexp='^%ADMIN ALL\=' line='%ADMIN ALL=(ALL) NOPASSWD:ALL' validate='visudo -cf %s'


# Install specified python requirements in indicated (virtualenv).
- pip: requirements=/my_app/requirements.txt virtualenv=/my_app/venv

# Install (Bottle) python package.
- pip: name=bottle


Django: it is a core module

add admin user with password

# Create an initial superuser.
- django_manage: command="createsuperuser --noinput --username=admin" app_path={{ django_dir }}

However, adding password requires user interaction. Workaround - based on tip, I created it to match the one in the comment. It works!

- hosts: all
    - secret.yml
    - name: Django add admin user and password
    shell: /bin/echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('{{ DJANGO_ADMIN_USER }}', '{{ DJANGO_ADMIN_EMAIL }}', '{{ DJANGO_ADMIN_PASSWORD }}') " | /usr/bin/python /vagrant/django/ shell  


  • run when called by notify
  • only runs once, even if there’s multiple notify
  • usually only used for restarting service or rebooting


- name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf
    - restart apache

    - name: restart apache
    service: name=apache state=restarted


Set variable

    field1: one
    field2: two

Read variable

foo['field1']  #recommended, since it doesn't interfere with python attrib
foo.field1    #warning: could interfere with existing python attributes. 
                # OReilly recommends this instead

{{ ansible_eth0["ipv4"]["address"] }} is same as {{ ansible_eth0.ipv4.address }}

{{ foo[0] }} Access the first element of an array:

Variables can be defined in playbooks

- hosts: webservers
    http_port: 80
    username: john

Make sure to quote inside YAML: app_path: "{{ base_path }}/app"

In Jinja2 templates, all variables are accessible: Hi {{username}}! Welcome!

To see all currently available vars (especially the built-in vars)

ansible hostname -m setup

Registered Var: save output of a task to these variables

# doc

 - shell: /usr/bin/foo
   register: foo_result   # save to foo_result
   ignore_errors: True

 - shell: /usr/bin/bar
   when: foo_result.rc == 5

Example (Ex 4.4 Using the output of a command in a task Ansible - Up and Running)

- name: capture output of id command
  command: id -un
  register: login
- debug: msg="Logged in as user {{ login.stdout }}"

Example (Ex 4-5 Ignoring when a module returns an error Ansible - Up and Running)

- name: Run myprog
  command: /opt/myprog
  register: result
  ignore_errors: True
- debug: var=result

Magic Variables (see doc)

Access var from another node


Enumerate within certain groups

# from doc.  enumerate in app_servers and get all IP address
{% for host in groups['app_servers'] %}
    {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}

Load external var file

  • useful for loading configuration, secrets, etc

    • hosts: all remote_user: root vars: favcolor: blue vars_files:
      • /vars/secret.yml ….

    in secret.yml

    somevar: somevalue password: magic

Pass var on command line --extra-vars or -e

ansible-playbook something.yml -e myName=Dan

ansible-playbook something.yml -e 'myTextVar="hello world"'

ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"

# in JSON
--extra-vars '{"pacman":"mrs","ghosts":["inky","pinky","clyde","sue"]}'

# in JSON file
--extra-vars "@some_file.json"

Access shell /env var

  • use “lookup” or “ansible_env.VAR”


    local_home: "{{ lookup('env','HOME') }}"
    local_home: " {{ansible_env.SOME_VARIABLE}} "

Built-in variables

(See Chapter 4 from Ansible - Up and Running)

  • hostvars
  • inventory_hostname
  • groups

    {% for host in groups.web %}
    server {{ host.inventory_hostname }} {{ host.ansible_default_ipv4.address }}:80
    {% endfor %}
  • group_names

  • play_hosts

  • ansible_version

  • ansible_env.*

Loops and iterations

Loops/iterations use item variable.

<module>: ... {{ item }}

Other variables

gather_facts: True
  • collects info about server, and saves in variable ansible_facts
  • other modules also save to various ansible_* variables

Playbook Galaxy

Ansible doc - Galaxy

Install 3rd party roles

ansible-galaxy install username.rolename

On Mac, error message “error: you do not have permission to modify files in /etc/…” Solution

ansible-galaxy install -p <ROLES_PATH>


$ vim ansible.cfg
roles_path = /path/to/all/roles:/second/path/to/roles:/third/path/roles

Roles are first searched in playbook dir. - more ansible playbooks.

Concise ansible playbooks (

Mac ansible playbooks installs homebrew, handbrake, nvalt, pckeyboardhack, skype sublime, vlc, virtualbox, .osx dotfile


Other commands

ansible-galaxy list

ansible-galaxy remove username.rolename

Playbook example detail

postgresql example

# Example Ansible playbook that uses the PostgreSQL module.
# This installs PostgreSQL on an Ubuntu system, creates a database called
# "myapp" and a user called "django" with password "mysupersecretpassword"
# with access to the "myapp" database.
- hosts: webservers
sudo: yes
gather_facts: no

- name: ensure apt cache is up to date
    apt: update_cache=yes
- name: ensure packages are installed
    apt: name={{item}}
        - postgresql
        - libpq-dev
        - python-psycopg2

- hosts: webservers
sudo: yes
sudo_user: postgres
gather_facts: no

    dbname: myapp
    dbuser: django
    dbpassword: mysupersecretpassword

- name: ensure database is created
    postgresql_db: name={{dbname}}

- name: ensure user has access to database
    postgresql_user: db={{dbname}} name={{dbuser}} password={{dbpassword}} priv=ALL

- name: ensure user does not have unnecessary privilege
    postgresql_user: name={{dbuser}} role_attr_flags=NOSUPERUSER,NOCREATEDB


Control machine

Control machine is where Ansible is installed, opposite of remote.

pip install ansible     # generic, all system, Mac OSX, latest v1.9

sudo apt-get install ansible   #debian/ubuntu (may require repository)
# Debian jessie at v1.7.2, latest version v2.0.0,


pip install ansible

If it shows error, use this:

sudo CFLAGS=-Qunused-arguments CPPFLAGS=-Qunused-arguments pip install ansible

Or Brew - currently at older version1.8

brew install ansible 

While it runs, OSX limits to 15 forks. May need to raise this limit:

sudo launchctl limit maxfiles 1024 2048


Config is read in the following order:

  1. ANSIBLE_CONFIG (an environment variable)
  2. ansible.cfg (file in the current directory, recommended for common)
  3. ~/.ansible.cfg (file in the home directory)
  4. /etc/ansible/ansible.cfg (global setting file)

Latest default configuration file (Ansible Github)

forks: probably the most important config value

forks=5  # should be changed to highest value possible. 
# see Mac OSX install section 

Example for usage in Vagrant:

hostfile = hosts
remote_user = vagrant
private_key_file = .vagrant/machines/default/virtualbox/private_key
host_key_checking = False

SSH forwarding

  • guest OS uses the same ssh key as the host OS user’s SSH key
  • useful for guest OS accessing github repository

    ssh_args = -o ForwardAgent=yes

ad-hoc commands

  • good for a once-only and quick run, but it is not permanent/saved.

non-shell command

ansible <group> -a "cmd" -f 10
# -m "aka command" is not needed, as it is default, if -m is not set
# -f 10 = fork 10 process.  Default=5. Higher the better, parallel 
# -a: arguments

by default, username is same as local system. To change,

ansible ...... -u username

ansible .... -u vagrant


ansible <group> -a "/sbin/fsck" -u username --sudo [--ask-sudo-pass]

shell-based command

ansible <group> -m shell -a 'echo $TERM'

File op


ansible <group> -m copy -a "src=~/scripts dest=/tmp/scripts"

change permission/owner

ansible <group> -m file -a "dest=/tmp/scripts mode=600"
ansible <group> -m file -a "dest=/tmp/scripts mode=600 owner=foo group=print"


ansible <group> -m file -a "dest=/tmp/newdir mode=755 state=directory"

rm -r

ansible <group> -m file -a "dest=/tmp/todelete state=absent"

package (apt/yum)


ansible <group> -m apt -a "name=htop state=present"


ansible <group> -m apt -a "name=htop state=absent"

user admin

$ ansible all -m user -a "name=foo password=<crypted password here>"

$ ansible all -m user -a "name=foo state=absent"


ansible webservers -m git -a "repo=git:// dest=/srv/myapp version=HEAD"


ansible webservers -m service -a "name=httpd state=started"

ansible webservers -m service -a "name=httpd state=restarted"

ansible webservers -m service -a "name=httpd state=stopped"

Ansible Tower Ansible Tower’s open source AWX



There’s no built-in module for Nginx.

Example of Nginx, but does not set config

- name: NGINX | Adding NGINX signing key
  apt_key: url= state=present

- name: NGINX | Adding sources.list deb url for NGINX
  lineinfile: dest=/etc/apt/sources.list line="deb jessie nginx"
  lineinfile: dest=/etc/apt/sources.list line="deb-src jessie nginx"

- name: update NGINX apt cache
  apt: update_cache=yes

- name: NGINX | Installing NGINX
  apt: pkg=nginx state=latest

- name: NGINX | Starting NGINX
  service: name=nginx state=started

Nginx playbooks

geerlingguy nginx

ansible-galaxy install geerlingguy.nginx
  • works for both Debian,Ubuntu and Redhat.
  • no dependency.
  • might be too simple.
  • any problem with multiple vhosts?
  • author of “Ansible for devops”.

Debops nginx

  • part of series for debops repository. excellent
  • debian-specific
  • quite complicated, no examples

ansible-galaxy install jdauphant.nginx
  • Ubuntu and Redhat
  • not completely compatible with Debian
  • has the highest rating in ansible galaxy

ANXS Nginx

  • Ubuntu focused,
  • ANXS - has too many dependencies on other ANXS modules?

Other Playbooks

RVM - official

$ ansible-galaxy install rvm_io.rvm1-ruby

Example .yml - Ruby V2.2.1


- name: Configure servers with ruby support
  hosts: all
        - 'ruby-2.2.1'

    - { role: rvm_io.rvm1-ruby, tags: ruby, sudo: True }


Bundler is an Extra Module

Examples from doc

# Installs version 1.0 of vagrant.
- gem: name=vagrant version=1.0 state=present

# Installs latest available version of rake.
- gem: name=rake state=latest

# Installs rake version 1.0 from a local gem on disk.
- gem: name=rake gem_source=/path/to/gems/rake-1.0.gem state=present


Bundler is an Extra Module

Examples from doc

# Installs gems from a Gemfile in the current directory
- bundler: state=present executable=~/.rvm/gems/2.1.5/bin/bundle

# Excludes the production group from installing
- bundler: state=present exclude_groups=production

# Only install gems from the default and production groups
- bundler: state=present deployment=yes

# Installs gems using a Gemfile in another directory
- bundler: state=present gemfile=../rails_project/Gemfile

# Updates Gemfile in another directory
- bundler: state=latest chdir=~/rails_project