I establish a LAMP with a few extras on a remote Debian machine with a similar shell (Bash) script:


apt update -y
apt upgrade ufw sshguard unattended-upgrades wget curl git zip unzip tree -y

ufw --force enable
ufw allow 22,25,80,443

apt upgrade lamp-server^ ssmtp
apt upgrade python-certbot-apache
apt upgrade php-{cli,curl,mbstring,mcrypt,gd} phpmyadmin

curl -sS https://getcomposer.org/installer -o composer-setup.php
php composer-setup.php --install-dir=/usr/local/bin --filename=composer

cat <<-EOF > /etc/php*/conf.d/local.ini
    upload_max_filesize = 2000M
    post_max_size = 2000M

a2enmod http2 deflate expires

Some sysadmins who saw a similar script told me sayings like "you better do it with Ansible because otherwise it will be a nightmare to maintain it".

Well, I can achieve basically the same with the following Ansible playbook I wrote (and have yet to test because I don't have a free machine right now but should work when deployed to all machines):


- hosts: all 
  become: yes
  become_user: root

    - name: Update apt package-indexes cache

    - name: Install external basics
        apt: state=latest
            - ufw
            - sshguard
            - unattended-upgrades
            - wget
            - curl
            - git
            - zip
            - unzip
            - tree

    - name: Setup firewall with ufw
        rule: allow
        port: 22,25,80,443

    - name: Establish a LAPMS server environment (Linux, Apache, PHP, MySQL, SSMTP)
        apt: state=latest
            - apache2 # Web server
            - python-certbot-apache
            - php
            - php-mysql # MySQL server 
            - php-cli
            - php-curl
            - php-mbstring
            - php-mcrypt
            - php-gd
            - phpmyadmin
            - ssmtp # Email server

    - name: Manually install PHP Composer
        url: https://getcomposer.org/installer
        dest: /tmp/composer-setup.php
      command: php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer

    - name: Configure PHP variables
      shell: |
        cat <<-EOF > /etc/php*/conf.d/local.ini
          upload_max_filesize = 2000M
          post_max_size = 2000M
        executable: /bin/bash

    - apache2_module:
    state: present
    name: http2

    - apache2_module:
    state: present
    name: deflate

    - apache2_module:
    state: present
    name: expires

My contemplation

I don't recognize a significant advantage of using mere Ansible over mere shell-scripting in the above trivial case as Ansible does only what we tell it to and doesn't do release_upgrades (as from Apache 2.4 to 3.4) and will basically give me the same apt upgrade -y automation with scheduling (say with cron) so I can't understand how it will be much of a helper (also the result is basically same but the number of lines is far greater (72 instead 20/21) unless one uses Ansible-Galaxy roles which are well organized programs containing a core of one or more Ansible playbooks.

My question

How does mere Ansible by itself (without using Ansible-Galaxy roles) more efficient than mere shell-scripting in general and Bash in particular in a trivial task such as the above?

closed as primarily opinion-based by muru, Mr Shunz, Rui F Ribeiro, sourcejedi, Jeff Schaller Dec 19 '18 at 16:20

Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. If this question can be reworded to fit the rules in the help center, please edit the question.

  • It is utterly disappointing to me that such an important-understanding question was closed. Please migrate to wherever you see fit: DevOps SE, SuperUser SE, ServerFault, StackOverflow, or whatever. This is the most important Ansible question I ever asked in my life in my opinion. Pleople, please help me to re-open it or flag it with me to migration. – JohnDoea Dec 20 '18 at 7:29
  • Another reason is that your bash script continues on errors, while Ansible stops on errors and reports them. It also reports which steps changed or didn't change a system. – r3m0t Dec 23 '18 at 10:02
  • While I do not exactly vouch for the quality of the question as it is, até you familiar with the tale of Peter and the wolf? – Rui F Ribeiro Jan 29 at 3:30

I do not think there is a big problem with 20 lines, as a shell script for an individual machine or two. That is a good thing to have.

That said, your script is not idempotent. For example it uses apt-get upgrade foo, which can perform changes even if the script has already been run on this system. I do not think it worth using apt-get upgrade foo. That command does not guarantee to apply security or bugfix updates to your dependencies.

A carefully written Ansible playbook can double as a small system check. This relies on the playbook being idempotent (and also that it reports "changed" status accurately). Running ansible-playbook --check will show whether the system still meets all the defined tasks, or whether it has been changed. This can both be useful later on, and also to look for inconsistencies immediately after running a playbook.

And you may set up Ansible so you can run this on several machines at once.

Configuration tools like Ansible are useful at scale. Partly due to these sorts of features. Partly due to these sorts of constraints, which it encourages you to follow.

Encouraging you to write idempotent scripts can also be useful for maintenance. Consider the temptation to write a script that adds lines to an existing file (or adds tokens inside an existing line :-( ). If you ensure idempotence, then you can re-run the modified script without adding duplicate lines.

Another constraint is if you ever want to remove things from an installed system. In many cases this is not a problem, and you could always write an uninstall script/playbook later on. But in some cases you need to be careful about this. E.g. you might want to make sure not to use Ansible lineinfile to replace an individual line, and instead overwrite the entire file with your own version. This can be useful when you no longer want to change line A from the default, but you still want to change line B.

For my usage, I would like to address the removal problem when managing firewalls. If you stop allowing a port in your install script, you might forget to explicitly block the port everywhere. The Ansible ufw module does not help here, because it only allows adding or removing individual rules. I am currently thinking about using an alternative to the ufw firewall, which has proper support for working from configuration files. (E.g. firewalld, shorewall, ferm ...).

  • 1
    The uninstallation angle is particularly hard to get right. Ideally, you want to be able to change your configuration description to omit the part you want to uninstall (since that’s the new target state for your system), and run your configuration management tool again. Thinking in terms of uninstallation playbooks often causes maintenance issues further down the line. – Stephen Kitt Dec 19 '18 at 13:31
  • I never understood that saying that Ansible is idempotent. It doesn't recursively operate on an operator to recursively bring the same result, it can bring different results (sorry, I didn't lean much math in the past and not sure I understand idempotent well enough to understand the answer, let alone in the Ansible context). – JohnDoea Dec 19 '18 at 13:31
  • @JohnDoea good Ansible playbooks are idempotent because they describe a desired state, not a desired installation process. If they describe a state, running them through Ansible ensures that the system is in the desired state, not that the desired configuration changes are applied. Applying the same state multiple times results in no change, so it’s idempotent. – Stephen Kitt Dec 19 '18 at 13:33
  • @JohnDoea HTTP DELETE method, or NFS write operations, are examples of operations which are described as idempotent. They have the same effect if you run them twice, as if you run them once. In NFS3 over UDP, this property allows write requests to be safely re-sent if the client does not receive a success response after a timeout. Even though it might be the response packet that was lost, not the write request packet. – sourcejedi Dec 19 '18 at 13:38
  • 1
    There are circumstances in which you want a list of all the allowed packages ;-). But yes, I certainly agree this is hard. It also leads to the whole pets-v.-cattle debate: in a cattle situation you’d nuke your VM and create a new one (no uninstallation needed), in the pet situation you need to deal with the ongoing maintenance of your system which always ends up having to deal with clean-up. – Stephen Kitt Dec 19 '18 at 13:59

The difference between Bash and Ansible is that Ansible, when written properly, is idempotent. There are no changes when a playbook runs repeatedly. In this respect Bash script describes a procedure while Ansible playbook rather describes a state of a system.

To address the comment

"not sure I understand idempotent well enough to understand the answer".

Idempotence means that a playbook "can be applied multiple times without changing the result beyond the initial application". Best practice is to run a playbook twice. During the first run a playbook performs all changes. During the second run no changes should be reported.

  • Dear Vladimir, thank you, but if so, anyone who says Ansible uses to automate upgrades is either mistaking or laying, right? I assume yes, but then we can say that Ansible-Galaxy roles are an exception for that idempotence, right? – JohnDoea Dec 24 '18 at 14:18
  • Dear John, to tell you the truth, my experience with "automatic upgrades" is limited to procedures provided by Vendors. Such procedures should be idempotent. But, in most cases I automatically read Release Notes, test and fix potential backward incompatibilities on my own with Ansible. – Vladimir Botka Dec 24 '18 at 15:27
  • Thanks Vladimir, but what are "vendors" in this context, please? – JohnDoea Dec 24 '18 at 20:49

One of the main selling points of Ansible is that playbooks tend to be easier to read than dense BASH one liners due to its use of YAML syntax. You can also use linters and debuggers in a more feature rich way than you can with BASH scripts, and some IDEs even have built in Ansible text completion modules to assist with writing playbooks.

  • Hey Timothy, is one such IDE is VSCODE? Because only with it I work and prefer to stick with it. Maybe you have a recommendation for An Ansible plugin for it? – JohnDoea Dec 20 '18 at 7:26
  • I've never used VSCODE before, but I have used Atom IDE which has several Ansible plugins for auto completion and linting capabilities to assist with writing playbooks. – Timothy Pulliam Dec 24 '18 at 14:19

Not the answer you're looking for? Browse other questions tagged or ask your own question.