Ansible Package & Service Modules: Cross-Distro Gotchas & Production Fixes
Master Ansible apt, yum, dnf, package, pip, service, and systemd modules.
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
- ✓Basic programming fundamentals
- ✓A computer with internet access
- ✓Willingness to follow along with examples
Ansible's package and service modules (e.g., apt, yum, service, systemd) let you manage software installation and daemon lifecycle declaratively. The key takeaway: always use state: present for packages and state: started with enabled: yes for services to ensure idempotent, boot-safe configurations.
Ansible provides a set of modules to manage packages and services across different operating systems. The apt, yum, and dnf modules are distribution-specific, each with its own set of parameters. The generic package module abstracts these differences: it detects the target system's package manager and delegates to the appropriate module.
This is essential for writing cross-distro playbooks.
The pip module installs Python packages from PyPI or other indexes. It manages pip itself if needed and can handle virtual environments.
For services, the service module is the traditional interface, but it lacks systemd-specific controls. The systemd module provides full access to systemd's unit management: daemon_reload, enabled, state, scope, and more. Using systemd is recommended for any system running systemd (most modern Linux distros).
These modules solve the problem of idempotent, declarative management of software and daemons. Instead of writing shell scripts that check state, you declare the desired state, and Ansible ensures it.
Imagine you're a restaurant manager. Different kitchens (distros) have different chefs (package managers): one chef uses a specific recipe book (apt), another uses a different one (yum). The package module is like a universal translator that tells each chef in their own language to prepare the same dish. The pip module is like a special order for a gourmet ingredient that only one supplier carries. Now, the service and systemd modules are like telling the waitstaff to start or stop a specific task (like turning on the espresso machine). The systemd module is the more detailed instruction that also tells the staff to reload the machine's settings (daemon_reload) and to ensure it starts automatically every morning (enabled). If you use the simpler service module on a modern kitchen, it might not reload settings, causing the machine to run with old instructions.
I once spent three hours debugging why a web server wouldn't start after a deployment. The playbook used the service module to restart nginx, but the unit file had been modified by a previous task. The service module didn't trigger a daemon-reload, so systemd was still using the old unit file. The symptom was a cryptic 'Failed to start nginx.service: Unit not found' error. That night, I learned the critical difference between service and systemd modules.
Historically, Ansible's service module was designed for SysV init systems, but modern distributions use systemd. The systemd module was introduced to provide full control over systemd units, including daemon-reload. Similarly, package management evolved from distro-specific modules (apt, yum) to the generic package module, which detects the underlying package manager.
This article covers the essential Ansible modules for managing packages and services in production. You'll learn when to use apt vs yum vs the generic package module, how to install Python packages with pip, and the nuances of service vs systemd. We'll also cover managing multiple services in a loop and real-world gotchas that can cause outages.
1. The Package Module: Cross-Distro Package Management
The package module is your Swiss Army knife for package management across different distributions. It automatically detects the system's package manager (apt, yum, dnf, zypper, pacman) and delegates to the appropriate module.
Basic usage: ``yaml - name: Install nginx on any distro package: name: nginx state: present ``
Production gotcha: The package module does not automatically update the package cache. On Debian/Ubuntu, this can lead to 'package not found' errors if the cache is stale. Always use update_cache with apt/yum/dnf, but note that package does not support update_cache directly. Instead, you must add a separate task: ```yaml - name: Update apt cache (Debian/Ubuntu) apt: update_cache: yes cache_valid_time: 3600 when: ansible_os_family == "Debian"
- name: Install nginx
- package:
- name: nginx
- state: present
- ```
Multiple packages: ``yaml - name: Install multiple packages package: name: - nginx - git - curl state: present ``
Version pinning: ``yaml - name: Install specific version of nginx package: name: nginx=1.18.0-0ubuntu1 state: present ` Note: Version syntax varies by distro. For apt, it's name=version; for yum/dnf, it's name-version-release`.
Production insight: In a mixed environment with CentOS 7 (yum) and Ubuntu 20.04 (apt), I used the package module but forgot to handle cache updates. The Ubuntu hosts failed with 'E: Unable to locate package nginx'. Adding a conditional apt task before the package task solved it.
Key takeaway: Use package for cross-distro playbooks, but always handle cache updates separately per distro family.
--- - name: Install packages across distros hosts: all tasks: - name: Install httpd (RHEL) or apache2 (Debian) ansible.builtin.package: name: "{{ 'httpd' if ansible_facts.os_family == 'RedHat' else 'apache2' }}" state: present
package module does not have an update_cache parameter. You must use the distro-specific module (apt, yum, dnf) to update the cache before using package.package module but forgot to handle cache updates. The Ubuntu hosts failed with 'E: Unable to locate package nginx'. Adding a conditional apt task before the package task solved it.package for cross-distro playbooks, but always handle cache updates separately per distro family.2. Apt Module: Debian/Ubuntu Specifics
The apt module manages packages on Debian-based systems. It supports update_cache, cache_valid_time, install_recommends, and more.
Basic install with cache update: ``yaml - name: Install nginx with cache update apt: name: nginx state: present update_cache: yes cache_valid_time: 3600 ` cache_valid_time` prevents cache update if it was updated within that many seconds – saves time in repeated runs.
Removing packages: ``yaml - name: Remove apache2 apt: name: apache2 state: absent purge: yes ` purge: yes` also removes configuration files.
Installing from a .deb file: ``yaml - name: Install a .deb package apt: deb: /tmp/mypackage.deb ``
Production gotcha: Using install_recommends: no to avoid pulling in recommended packages: ``yaml - name: Install minimal nginx apt: name: nginx state: present install_recommends: no ``
Key takeaway: Use cache_valid_time to optimize playbook speed and install_recommends to control dependency bloat.
--- - name: Install nginx on Debian/Ubuntu hosts: debian tasks: - name: Add nginx repository ansible.builtin.apt_repository: repo: deb http://nginx.org/packages/ubuntu/ {{ ansible_distribution_release }} nginx state: present - name: Install nginx ansible.builtin.apt: name: nginx state: latest update_cache: yes
cache_valid_time: 3600 to only update the apt cache if it's older than 1 hour. This speeds up subsequent playbook runs.cache_valid_time, it updated the apt cache every time, adding 30 seconds to every run. Adding cache_valid_time: 3600 reduced the pipeline time significantly.cache_valid_time to optimize playbook speed and install_recommends to control dependency bloat.3. Yum and Dnf Modules: RHEL/CentOS/Fedora Specifics
The yum module is for RHEL/CentOS 7 and older, while dnf is for Fedora and RHEL 8+. Both support update_cache, enablerepo, disablerepo, and allow_downgrade.
Basic install: ``yaml - name: Install nginx on CentOS 7 yum: name: nginx state: present update_cache: yes ``
For RHEL 8+/Fedora: ``yaml - name: Install nginx on RHEL 8 dnf: name: nginx state: present update_cache: yes ``
Installing from a specific repository: ``yaml - name: Install from EPEL yum: name: nginx enablerepo: epel state: present ``
Disabling a repo: ``yaml - name: Install without certain repo yum: name: nginx disablerepo: '*' enablerepo: base,updates state: present ``
Production gotcha: Downgrading packages requires allow_downgrade: yes: ``yaml - name: Downgrade nginx to 1.18 yum: name: nginx-1.18.0-1.el7 state: present allow_downgrade: yes ``
Key takeaway: Use enablerepo/disablerepo to control which repos are used, and allow_downgrade for version rollbacks.
--- - name: Install httpd on RHEL/CentOS/Fedora hosts: rhel tasks: - name: Install httpd ansible.builtin.yum: name: httpd state: present enablerepo: base,updates - name: Start and enable httpd ansible.builtin.systemd: name: httpd state: started enabled: yes
yum is symlinked to dnf, but it's best to use the dnf module explicitly for clarity. For CentOS 7, use yum.allow_downgrade: yes, the task failed with 'Package is already installed'. Adding that parameter fixed it.enablerepo/disablerepo to control which repos are used, and allow_downgrade for version rollbacks.4. The Pip Module: Python Package Management
The pip module installs Python packages from PyPI or other indexes. It can manage pip itself and work with virtual environments.
Basic install: ``yaml - name: Install requests library pip: name: requests state: present ``
Version pinning: ``yaml - name: Install specific version pip: name: requests==2.25.1 state: present ``
Upgrading pip: ``yaml - name: Upgrade pip pip: name: pip state: latest ``
Installing from requirements file: ``yaml - name: Install from requirements.txt pip: requirements: /path/to/requirements.txt ``
Virtual environment: ``yaml - name: Install package in virtualenv pip: name: flask virtualenv: /opt/myapp/venv virtualenv_python: python3.8 ``
Production gotcha: Without state: present, if the package is already installed, it won't be upgraded. Use state: latest to force upgrade, but be careful with breaking changes. Always pin versions in production.
Key takeaway: Pin package versions and use virtualenv for isolated environments.
--- - name: Install Python packages with pip hosts: all tasks: - name: Install pip if not present ansible.builtin.package: name: python3-pip state: present - name: Install required Python packages ansible.builtin.pip: name: - flask - requests state: present executable: pip3
state: latest can pull in breaking changes. Always pin to a specific version in production playbooks.state: latest for a critical library, which upgraded from v1 to v2 and broke our API. We now enforce version pinning in all pip tasks.virtualenv for isolated environments.5. Service Module: The Legacy Approach
The service module is the traditional way to manage services. It works with SysV init, Upstart, and systemd, but with limited control.
Basic usage: ``yaml - name: Start nginx service: name: nginx state: started enabled: yes ``
Supported states: started, stopped, restarted, reloaded.
Production gotcha: The service module does not trigger daemon-reload on systemd systems. If you modify a unit file, the service may fail to start. Always use systemd module if you need daemon-reload.
When to use service: For non-systemd systems (e.g., older Ubuntu with Upstart, or containers without systemd).
Key takeaway: Prefer systemd over service on modern Linux. Only use service for legacy init systems.
--- - name: Manage httpd service (legacy) hosts: rhel tasks: - name: Ensure httpd is running and enabled ansible.builtin.service: name: httpd state: started enabled: yes
service module never runs systemctl daemon-reload. If you change a unit file, the service may not start correctly. Use systemd module instead.service to restart a service after updating its unit file. The service failed to start because systemd didn't know about the new unit. We lost 30 minutes of uptime.systemd over service on modern Linux. Only use service for legacy init systems.6. Systemd Module: Full Control Over systemd Units
The systemd module provides complete control over systemd units, including daemon_reload, enabled, state, scope, and more.
Basic usage: ``yaml - name: Start and enable nginx systemd: name: nginx state: started enabled: yes daemon_reload: yes ``
Daemon-reload only when unit file changes: Use a handler: ```yaml tasks: - name: Copy unit file template: src: myapp.service.j2 dest: /etc/systemd/system/myapp.service notify: reload systemd
handlers: - name: reload systemd systemd: daemon_reload: yes listen: "reload systemd" ```
Other parameters: - scope: user for user services (e.g., --user flag). - no_block: yes to not wait for job completion (async). - masked: yes to mask a unit.
Production gotcha: Forgetting daemon_reload: yes after changing unit files is the #1 mistake.
Key takeaway: Always use systemd for systemd-based systems and set daemon_reload: yes when unit files change.
--- - name: Full control over nginx systemd unit hosts: debian tasks: - name: Ensure nginx is running and enabled ansible.builtin.systemd: name: nginx state: started enabled: yes daemon_reload: yes
daemon_reload only when unit files change using a handler. This avoids unnecessary reloads on every playbook run.systemd for systemd-based systems and set daemon_reload: yes when unit files change.7. Managing Multiple Services in a Loop
Often you need to manage several services (e.g., web server, app server, database). Use loop with a list.
Example: Start multiple services: ``yaml - name: Start required services systemd: name: "{{ item }}" state: started enabled: yes loop: - nginx - postgresql - myapp ``
Conditional loops: ``yaml - name: Enable services based on role systemd: name: "{{ item }}" enabled: yes state: started loop: "{{ services_to_enable }}" when: services_to_enable is defined ``
Using with_items (older syntax): ``yaml - name: Stop services service: name: "{{ item }}" state: stopped with_items: - apache2 - mysql ``
Production gotcha: When looping, ensure the service names are correct for each distro. For example, Apache is httpd on RHEL and apache2 on Debian. Use variables or ansible_facts.
Key takeaway: Use loop to manage multiple services, but account for distro-specific service names.
--- - name: Manage multiple services in a loop hosts: all vars: services: - nginx - postgresql - redis tasks: - name: Ensure services are running and enabled ansible.builtin.systemd: name: "{{ item }}" state: started enabled: yes loop: "{{ services }}"
httpd on RHEL, apache2 on Debian. MySQL: mysqld on RHEL, mysql on Debian. Use variables to handle differences.services: { debian: { apache: apache2 }, redhat: { apache: httpd } }.loop to manage multiple services, but account for distro-specific service names.8. State and Enabled: Understanding the Difference
state controls the immediate runtime state (started, stopped, restarted, reloaded). enabled controls whether the service starts at boot.
Combinations: - state: started and enabled: yes: Start now and persist on reboot. - state: stopped and enabled: no: Stop now and disable on boot. - state: restarted with enabled: yes: Restart now and ensure enabled.
Idempotency: Ansible checks the current state and only acts if needed. For example, if the service is already started, state: started does nothing.
Production gotcha: Using state: restarted without enabled will restart the service but not ensure it's enabled. If the service was disabled, after a reboot it won't start.
Key takeaway: Always set both state and enabled to ensure desired runtime and boot behavior.
--- - name: Demonstrate state vs enabled hosts: all tasks: - name: Start nginx but do not enable ansible.builtin.systemd: name: nginx state: started enabled: no - name: Enable nginx but do not start ansible.builtin.systemd: name: nginx state: stopped enabled: yes
state: restarted only restarts the service. It does not set enabled. If you want the service to start on boot, add enabled: yes.state: restarted without enabled. After a reboot, the service was down. We now have a linting rule that flags any state: restarted without enabled.state and enabled to ensure desired runtime and boot behavior.9. Daemon_reload: When and Why
daemon_reload tells systemd to reload its configuration, picking up changes to unit files. It is equivalent to running systemctl daemon-reload.
When to use: - After adding, removing, or modifying unit files in /etc/systemd/system/. - After changing symlinks in /etc/systemd/system/multi-user.target.wants/.
When not needed: - If you're just starting/stopping an existing service without changing unit files. - If the unit file is in /usr/lib/systemd/system/ (system packages), but changes there are rare.
Performance impact: Running daemon_reload is fast but can be expensive if done on every playbook run. Use handlers to trigger only when needed.
Example with handler: ```yaml tasks: - name: Copy unit file template: src: myapp.service.j2 dest: /etc/systemd/system/myapp.service notify: reload systemd
handlers: - name: reload systemd systemd: daemon_reload: yes ```
Key takeaway: Use daemon_reload: yes only when unit files change; use handlers to avoid unnecessary reloads.
--- - name: Reload systemd after unit file change hosts: all tasks: - name: Copy custom systemd unit file ansible.builtin.copy: src: myapp.service dest: /etc/systemd/system/myapp.service notify: reload systemd handlers: - name: reload systemd ansible.builtin.systemd: daemon_reload: yes
systemd: daemon_reload=yes multiple times is safe; it simply reloads the daemon. But it's still a waste of time if no unit files changed.daemon_reload: yes on every run, adding 2 seconds per host. With hundreds of hosts, that added up. Moving it to a handler saved minutes.daemon_reload: yes only when unit files change; use handlers to avoid unnecessary reloads.10. Common Trap: service vs systemd on systemd Systems
The service module works on systemd systems but lacks daemon_reload. It uses systemctl under the hood, but without the daemon-reload command.
What happens: - service: name=myapp state=restarted runs systemctl restart myapp. - If the unit file was changed, systemd uses the old cached version. - The service may fail to start or run with old settings.
Why it's a trap: The service module doesn't error; it returns success even if the service fails to start (depending on the state). The error may only appear in logs.
Detection: After a playbook run, check systemctl status myapp and systemctl show myapp to see if the unit file is the expected one.
Best practice: Use systemd module exclusively on systemd-based systems. Reserve service for non-systemd systems (e.g., Docker containers without systemd, or legacy init).
Key takeaway: On systemd systems, always use systemd module to avoid silent failures.
--- - name: Trap: service vs systemd on systemd systems hosts: all tasks: - name: Use service module (works but less control) ansible.builtin.service: name: nginx state: restarted - name: Use systemd module (preferred) ansible.builtin.systemd: name: nginx state: restarted daemon_reload: yes
service module may return success even if the service fails to start after a unit file change, because it doesn't check for daemon-reload issues.systemd with daemon_reload fixed it.systemd module to avoid silent failures.11. Cross-Distro Playbook Patterns for Packages and Services
Writing playbooks that work on both Debian and RHEL families requires careful handling of package names, service names, and cache updates.
Package names: Use variables: ``yaml vars: packages: - nginx - git debian_packages: - python3-apt redhat_packages: - python3-dnf ``
Service names: ``yaml vars: apache_service: "{{ 'httpd' if ansible_os_family == 'RedHat' else 'apache2' }}" ``
Cache update pattern: ```yaml - name: Update package cache (Debian) apt: update_cache: yes cache_valid_time: 3600 when: ansible_os_family == "Debian"
- name: Update package cache (RedHat)
- yum:
- update_cache: yes
- when: ansible_os_family == "RedHat"
- ```
Using the generic package module: ``yaml - name: Install packages package: name: "{{ item }}" state: present loop: "{{ packages }}" ``
Key takeaway: Use ansible_os_family and variables to handle distro-specific differences.
--- - name: Cross-distro package and service management hosts: all tasks: - name: Install web server ansible.builtin.package: name: "{{ 'httpd' if ansible_facts.os_family == 'RedHat' else 'apache2' }}" state: present - name: Start and enable web server ansible.builtin.systemd: name: "{{ 'httpd' if ansible_facts.os_family == 'RedHat' else 'apache2' }}" state: started enabled: yes
ansible_os_family and variables to handle distro-specific differences.12. Advanced: Using Handlers with Package and Service Modules
Handlers are tasks that run only when notified. They are perfect for service restarts after configuration changes.
Pattern: Restart service after config change: ```yaml tasks: - name: Update nginx config template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf notify: restart nginx
handlers: - name: restart nginx systemd: name: nginx state: restarted daemon_reload: yes ```
Multiple handlers for different services: ```yaml tasks: - name: Update app config template: src: app.conf.j2 dest: /opt/myapp/app.conf notify: restart myapp
- name: Update nginx config
- template:
- src: nginx.conf.j2
- dest: /etc/nginx/nginx.conf
- notify: restart nginx
handlers: - name: restart myapp systemd: name: myapp state: restarted
- name: restart nginx
- systemd:
- name: nginx
- state: restarted
- ```
Handlers with package installation: ```yaml tasks: - name: Install nginx package: name: nginx state: present notify: start nginx
handlers: - name: start nginx systemd: name: nginx state: started enabled: yes ```
Production gotcha: Handlers run at the end of the play, not immediately. If a later task depends on the service being restarted, you may need to use meta: flush_handlers.
Key takeaway: Use handlers to trigger service restarts only when configuration changes, and use flush_handlers if immediate restart is needed.
--- - name: Use handlers with package and service modules hosts: all tasks: - name: Install nginx ansible.builtin.package: name: nginx state: present notify: restart nginx - name: Update nginx config ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf notify: restart nginx handlers: - name: restart nginx ansible.builtin.systemd: name: nginx state: restarted daemon_reload: yes
- meta: flush_handlers to run handlers immediately.meta: flush_handlers after the deploy task fixed it.flush_handlers if immediate restart is needed.The Missing daemon_reload: A Service Outage
service module does not call systemctl daemon-reload. The unit file was updated, but systemd was still using the old cached version, so the service name wasn't recognized.service with systemd module and set daemon_reload: yes before restarting. Also added a handler to run daemon-reload only when unit files change.- Always use the
systemdmodule for systemd-based systems, and explicitly trigger daemon-reload after modifying unit files.
update_cache: yes to the package task. Example: apt: name=nginx state=present update_cache=yesdaemon_reload. Fix: use systemd: name=myapp daemon_reload=yes state=restarted.timeout parameter (e.g., apt: timeout=300) or use async with poll.enabled: yes. Fix: systemd: name=myapp enabled=yes state=started.ansible all -m apt -a 'update_cache=yes'ansible all -m apt -a 'name=nginx state=present update_cache=yes'update_cache: yes to your playbook taskansible all -m systemd -a 'daemon_reload=yes'ansible all -m systemd -a 'name=myapp state=restarted'service with systemd module and set daemon_reload: yesansible all -m systemd -a 'name=myapp'ansible all -m systemd -a 'name=myapp enabled=yes'enabled: yes to your service taskansible all -m package -a 'name=nginx state=present'apt: or yum: with package:ansible all -m pip -a 'name=requests version=2.25.1'ansible all -m pip -a 'name=requests state=latest'pip: name=pip state=latest| Feature | apt | yum | dnf | package |
|---|---|---|---|---|
| update_cache | yes | yes | yes | no (use distro-specific) |
| cache_valid_time | yes | no | no | no |
| install_recommends | yes | no | no | no |
| enablerepo/disablerepo | no | yes | yes | no |
| allow_downgrade | no | yes | yes | no |
| Cross-distro | Debian only | RHEL 7 only | RHEL 8+ | All distros |
| File | Command / Code | Purpose |
|---|---|---|
| install-packages.yml | - name: Install packages across distros | 1. The Package Module |
| install-nginx-apt.yml | - name: Install nginx on Debian/Ubuntu | 2. Apt Module |
| install-httpd-yum.yml | - name: Install httpd on RHEL/CentOS/Fedora | 3. Yum and Dnf Modules |
| install-pip-packages.yml | - name: Install Python packages with pip | 4. The Pip Module |
| start-httpd-service.yml | - name: Manage httpd service (legacy) | 5. Service Module |
| manage-nginx-systemd.yml | - name: Full control over nginx systemd unit | 6. Systemd Module |
| manage-multiple-services.yml | - name: Manage multiple services in a loop | 7. Managing Multiple Services in a Loop |
| state-vs-enabled.yml | - name: Demonstrate state vs enabled | 8. State and Enabled |
| daemon-reload.yml | - name: Reload systemd after unit file change | 9. Daemon_reload |
| service-vs-systemd.yml | - name: Trap: service vs systemd on systemd systems | 10. Common Trap |
| cross-distro-package-service.yml | - name: Cross-distro package and service management | 11. Cross-Distro Playbook Patterns for Packages and Services |
| handlers-package-service.yml | - name: Use handlers with package and service modules | 12. Advanced |
Key takeaways
package module for cross-distro playbooks, but handle cache updates separately. and cache_valid_time` for apt, yum, dnf tasks.systemd over service module on modern Linux systems.state and enabled to control runtime and boot behavior.ansible_os_family to handle distro-specific service and package names.Interview Questions on This Topic
What is the difference between the `service` and `systemd` modules in Ansible?
service module is a generic module that works with SysV init, Upstart, and systemd. However, it does not support daemon_reload, which is required after modifying systemd unit files. The systemd module is specific to systemd and provides full control, including daemon_reload, scope, masked, and more. For modern Linux distributions using systemd, the systemd module is recommended.How do you handle cross-distro package management in Ansible?
package module, which automatically detects the underlying package manager (apt, yum, dnf, zypper, etc.). However, the package module does not support update_cache. To update the cache, you need to add a separate task using the distro-specific module (e.g., apt: update_cache=yes) conditionally based on ansible_os_family.What is the purpose of `daemon_reload` in the `systemd` module?
daemon_reload tells systemd to reload its configuration, picking up changes to unit files. It is equivalent to running systemctl daemon-reload on the command line. It is necessary after adding, removing, or modifying unit files in /etc/systemd/system/. Without it, systemd may use a cached version of the unit file, causing the service to fail or behave incorrectly.How can you install multiple packages with a single task in Ansible?
name parameter of the package, apt, yum, or dnf module. For example: package: name: [nginx, git, curl] state: present. Alternatively, you can use a loop: loop: "{{ packages }}" where packages is a list variable.Explain the difference between `state: started` and `state: restarted` in the `systemd` module.
state: started ensures the service is running; if it's already running, it does nothing. state: restarted always restarts the service, even if it's already running. Use restarted when you want to apply configuration changes that require a restart. Note that restarted does not set enabled, so if you want the service to start on boot, you must also specify enabled: yes.How do you manage a service that should be started only if a certain condition is met?
when clause on the service task. For example: systemd: name=myapp state=started enabled=yes when: myapp_enabled. You can also use conditionals based on variables or facts.What is the best practice for ensuring a service restarts only when its configuration file changes?
notify: restart service. Define a handler named 'restart service' that uses the systemd module to restart the service. Handlers run at the end of the play, so if you need the service restarted immediately (e.g., before a subsequent task), use meta: flush_handlers.How can you install a Python package from a private repository using the `pip` module?
extra_args parameter to pass additional pip arguments. For example: pip: name=mypackage extra_args='-i https://private-pypi.example.com/simple'. You can also use requirements with a file that specifies the index.Frequently Asked Questions
No, the package module does not support update_cache. You must use distro-specific modules like apt or yum with update_cache: yes.
state: present ensures the package is installed but does not upgrade it if already installed. state: latest ensures the latest version is installed, which may cause upgrades.
No, only after modifying unit files. Starting an existing service does not require daemon_reload.
Use the systemd module with no state: systemd: name=myapp. The output includes enabled and status information.
Yes, but Docker containers often don't run systemd. You may need to use the service module or shell commands, depending on the container's init system.
It specifies the path to a Python virtual environment. The package will be installed into that environment instead of the system Python.
Use variables with conditional values based on ansible_os_family or ansible_distribution. For example, apache: "{{ 'httpd' if ansible_os_family == 'RedHat' else 'apache2' }}".
Yes, with yum or dnf modules using allow_downgrade: yes. For apt, you can specify the version string directly.
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
That's Ansible. Mark it forged?
7 min read · try the examples if you haven't