Ansible has various ways of looking up data from outside sources, including plain text password files, CSV files and INI files. But it doesn’t seem to have a lookup for .env files, as used in Laravel projects, also available for PHP, Ruby, Node.js, Python and others.

One option is to launch Ansible with the Ruby dotenv command line script… But that requires Ruby, which seems like overkill to me. (Oct 2018: Pavel Savchenko commented that there’s also python-dotenv. Daniel Kossakowski pointed out that buffers the output – but I haven’t tried it myself.)

So here is a simpler solution that I use. It consists of:

  1. The .env file itself
  2. A ansible-playbook.sh wrapper script loads the .env file into environment variables and exports them to ansible-playbook
  3. The Ansible playbook reads from the environment variables into variables

.env

SUDO_PASSWORD='correct battery horse staple'
HOSTNAME='example.com'
INSTALL_APACHE=true
# etc.

Note: If the file contains passwords, like this example, make sure it’s suitably protected.

ansible-playbook.sh

#!/bin/bash
set -o nounset -o pipefail -o errexit

# Load all variables from .env and export them all for Ansible to read
set -o allexport
source "$(dirname "$0")/.env"
set +o allexport

# Run Ansible
exec ansible-playbook "$@"

playbook.yml

- hosts: all
  become: true # Need sudo for most tasks

  # These variables are needed to bootstrap Ansible
  vars:
    ansible_sudo_password: '{{ lookup("env", "SUDO_PASSWORD") }}'
    # etc.

  # Use 'set_fact' not 'vars' for the rest of the variables to ensure they are are evaluated immediately, to avoid getting this warning later:
  # "[WARNING]: when statements should not include jinja2 templating delimiters" (see https://github.com/ansible/ansible/issues/22397)
  pre_tasks:
    - name: Loading environment variables
      tags: always
      set_fact:
        # I write the variables in uppercase so they match the .env file - but you don't have to
        # For booleans I use the strings "true" and "false", and convert them to booleans at this point
        HOSTNAME: '{{ lookup("env", "HOSTNAME") }}'
        INSTALL_APACHE: '{{ lookup("env", "INSTALL_APACHE") == "true" }}'
        # etc.