Ansible Environment Variables — Set and Use in Playbooks
Introduction
Environment variables configure how programs behave — database URLs, API keys, proxy settings, PATH, and more. Ansible lets you set environment variables at the task, play, block, or role level using the environment keyword. This guide covers every technique for managing environment variables in your automation.
Task-Level Environment
---
- name: Environment variable examples
hosts: all
tasks:
- name: Run command with custom env
ansible.builtin.command:
cmd: /opt/app/deploy.sh
environment:
APP_ENV: production
DATABASE_URL: "postgresql://db:5432/myapp"
LOG_LEVEL: info
- name: Build with custom PATH
ansible.builtin.command:
cmd: make build
chdir: /opt/app
environment:
PATH: "/opt/go/bin:/usr/local/bin:{{ ansible_env.PATH }}"
GOPATH: /opt/go
Play-Level Environment
Set once, applies to all tasks in the play:
---
- name: Deploy application
hosts: webservers
become: true
environment:
APP_ENV: production
RAILS_ENV: production
NODE_ENV: production
tasks:
- name: Run migrations
ansible.builtin.command:
cmd: bundle exec rails db:migrate
chdir: /opt/app
# Inherits APP_ENV, RAILS_ENV, NODE_ENV
- name: Compile assets
ansible.builtin.command:
cmd: bundle exec rails assets:precompile
chdir: /opt/app
# Also inherits the play environment
Block-Level Environment
- name: Java tasks with JAVA_HOME
environment:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk
PATH: "/usr/lib/jvm/java-17-openjdk/bin:{{ ansible_env.PATH }}"
block:
- name: Build Java app
ansible.builtin.command:
cmd: mvn clean package
chdir: /opt/app
- name: Run tests
ansible.builtin.command:
cmd: mvn test
chdir: /opt/app
Using Variables for Environment
---
- name: Environment from variables
hosts: webservers
vars:
app_environment:
DATABASE_URL: "{{ vault_database_url }}"
REDIS_URL: "{{ vault_redis_url }}"
SECRET_KEY: "{{ vault_secret_key }}"
APP_ENV: "{{ env | default('production') }}"
environment: "{{ app_environment }}"
tasks:
- name: Start application
ansible.builtin.systemd:
name: myapp
state: restarted
Proxy Configuration
---
- name: Configure hosts behind proxy
hosts: all
become: true
environment:
http_proxy: "http://proxy.example.com:3128"
https_proxy: "http://proxy.example.com:3128"
no_proxy: "localhost,127.0.0.1,.example.com"
tasks:
- name: Install packages (uses proxy)
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Download file (uses proxy)
ansible.builtin.get_url:
url: https://example.com/file.tar.gz
dest: /tmp/file.tar.gz
Persistent Environment Variables
Set permanent environment variables on the target:
# System-wide via /etc/environment
- name: Set system environment variable
ansible.builtin.lineinfile:
path: /etc/environment
regexp: '^APP_ENV='
line: 'APP_ENV=production'
# Per-user via .bashrc
- name: Set user environment variable
ansible.builtin.lineinfile:
path: "/home/{{ app_user }}/.bashrc"
regexp: '^export APP_ENV='
line: 'export APP_ENV=production'
# Via profile.d (all users)
- name: Create app profile
ansible.builtin.copy:
content: |
export APP_ENV=production
export APP_HOME=/opt/app
export PATH=$APP_HOME/bin:$PATH
dest: /etc/profile.d/myapp.sh
mode: '0644'
# Systemd service environment
- name: Create systemd override
ansible.builtin.copy:
content: |
[Service]
Environment="DATABASE_URL={{ vault_database_url }}"
Environment="REDIS_URL={{ vault_redis_url }}"
dest: /etc/systemd/system/myapp.service.d/env.conf
mode: '0600'
notify: Restart myapp
Reading Remote Environment
# ansible_env contains the remote user's environment
- name: Show remote PATH
ansible.builtin.debug:
msg: "Remote PATH: {{ ansible_env.PATH }}"
- name: Show all remote env vars
ansible.builtin.debug:
var: ansible_env
# Read specific env var from remote
- name: Check JAVA_HOME
ansible.builtin.debug:
msg: "JAVA_HOME is {{ ansible_env.JAVA_HOME | default('NOT SET') }}"
Controller Environment Variables
# Pass env vars to ansible-playbook
APP_VERSION=2.0 ansible-playbook deploy.yml
# Access in playbook with lookup
- name: Use controller env var
ansible.builtin.debug:
msg: "Deploying version {{ lookup('env', 'APP_VERSION') }}"
# Common pattern: fall back to default
- name: Get version from env or default
ansible.builtin.set_fact:
deploy_version: "{{ lookup('env', 'APP_VERSION') | default('latest', true) }}"
Environment File Pattern
# Load from .env file
- name: Read .env file
ansible.builtin.slurp:
src: /opt/app/.env
register: env_file
- name: Parse .env file
ansible.builtin.set_fact:
app_env: "{{ dict(env_file.content | b64decode | split('\n') | select | map('split', '=', 1) | list) }}"
Troubleshooting
| Issue | Solution |
|---|---|
| Env var not visible | environment only applies to that task's process |
| PATH not working | Append to existing: "{{ ansible_env.PATH }}" |
| Proxy not used | Set both http_proxy and https_proxy (lowercase) |
| become resets env | Use become_flags: '-E' to preserve environment |
| Env var has quotes | Don't double-quote: APP_ENV: production not APP_ENV: "'production'" |
Best Practices
- Use Vault for secrets — never hardcode API keys or passwords
- Define at play level for consistency — avoid per-task duplication
- Use variables for environments —
environment: "{{ app_env }}"with per-env var files - Append to PATH — don't replace:
/opt/bin:{{ ansible_env.PATH }} - Use systemd Environment files for services — not
.bashrc
Conclusion
The environment keyword gives you fine-grained control over process environment variables at every level — task, block, play, and role. Use it for proxies, build tools, application configuration, and anything that needs environment context. For persistent variables, use lineinfile with /etc/environment, profile.d, or systemd overrides.