Introduction
Keeping your fleet of Debian-based servers consistently updated is one of the most critical — and time-consuming — tasks in system administration. The Ansible ansible.builtin.apt module automates package updates across Debian, Ubuntu, Linux Mint, Kali Linux, and all other APT-based distributions. This article covers single-package updates, full system upgrades, rolling update strategies with serial, reboot handling, and production best practices.
Module Overview
The ansible.builtin.apt module manages packages on Debian-like systems. For rolling updates, the key parameters are:
| Parameter | Type | Description |
|---|---|---|
name | string/list | Package name(s), or "*" for all packages |
state | string | present, absent, latest, fixed |
update_cache | boolean | Run apt-get update before install |
cache_valid_time | integer | Skip cache update if refreshed within N seconds |
upgrade | string | no, safe, full, dist |
autoremove | boolean | Remove unused dependencies |
autoclean | boolean | Clean local repository of old packages |
force_apt_get | boolean | Use apt-get instead of aptitude |
dpkg_options | string | Additional dpkg options |
only_upgrade | boolean | Only upgrade, don't install new |
Update Strategies
Strategy 1: Update a Single Package
---
- name: Update nginx
hosts: webservers
become: true
tasks:
- name: Ensure nginx is latest
ansible.builtin.apt:
name: nginx
state: latest
update_cache: true
Strategy 2: Update All Packages
---
- name: Full system update
hosts: all
become: true
tasks:
- name: Update all packages
ansible.builtin.apt:
name: "*"
state: latest
update_cache: true
Strategy 3: Safe Upgrade
Uses aptitude safe-upgrade — only upgrades packages that don't require removing other packages:
---
- name: Safe upgrade
hosts: all
become: true
tasks:
- name: Safe upgrade all packages
ansible.builtin.apt:
upgrade: safe
update_cache: true
Strategy 4: Full Upgrade
Uses aptitude full-upgrade — may remove packages if needed for the upgrade:
---
- name: Full upgrade
hosts: all
become: true
tasks:
- name: Full upgrade all packages
ansible.builtin.apt:
upgrade: full
update_cache: true
Strategy 5: Distribution Upgrade
Uses apt-get dist-upgrade — handles changing dependencies, commonly used for major version upgrades:
---
- name: Distribution upgrade
hosts: all
become: true
tasks:
- name: Dist upgrade
ansible.builtin.apt:
upgrade: dist
update_cache: true
Rolling Updates with serial
In production, you should never update all servers at once. Use serial to update servers in batches:
---
- name: Rolling update - web tier
hosts: webservers
become: true
serial: 2 # Update 2 servers at a time
max_fail_percentage: 25
tasks:
- name: Update all packages
ansible.builtin.apt:
upgrade: safe
update_cache: true
register: apt_result
- name: Display updated packages
ansible.builtin.debug:
msg: "Updated packages: {{ apt_result.stdout_lines | default([]) }}"
when: apt_result.changed
Serial Options
# Fixed number
serial: 1 # One at a time (safest)
serial: 3 # Three at a time
# Percentage
serial: "25%" # 25% of hosts at a time
# Escalating batches
serial:
- 1 # First: 1 host (canary)
- 3 # Then: 3 hosts
- "50%" # Then: 50% of remaining
Complete Rolling Update Playbook
A production-ready playbook with pre-checks, update, reboot handling, and verification:
---
- name: Rolling update Debian servers
hosts: all
become: true
serial: 1
max_fail_percentage: 0
pre_tasks:
- name: Check if server is healthy
ansible.builtin.uri:
url: "http://{{ inventory_hostname }}/health"
status_code: 200
register: health_check
failed_when: health_check.status != 200
when: health_check_enabled | default(false)
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600 # Skip if updated in last hour
- name: Upgrade all packages (safe)
ansible.builtin.apt:
upgrade: safe
register: upgrade_result
- name: Remove unused dependencies
ansible.builtin.apt:
autoremove: true
when: upgrade_result.changed
- name: Clean apt cache
ansible.builtin.apt:
autoclean: true
- name: Check if reboot is required
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_file
- name: Reboot if required
ansible.builtin.reboot:
msg: "Reboot triggered by system updates"
reboot_timeout: 300
pre_reboot_delay: 5
post_reboot_delay: 30
when: reboot_file.stat.exists
post_tasks:
- name: Verify server is back online
ansible.builtin.wait_for:
port: 22
timeout: 120
delegate_to: localhost
become: false
Handling Reboots
Some updates (kernel, glibc, systemd) require a reboot. Detect and handle this:
- name: Check reboot required
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
- name: Display packages requiring reboot
ansible.builtin.command: cat /var/run/reboot-required.pkgs
register: reboot_pkgs
when: reboot_required.stat.exists
changed_when: false
- name: Show reboot packages
ansible.builtin.debug:
msg: "Reboot needed for: {{ reboot_pkgs.stdout_lines }}"
when: reboot_required.stat.exists
- name: Reboot the server
ansible.builtin.reboot:
reboot_timeout: 300
when: reboot_required.stat.exists
Security Updates Only
Install only security updates using unattended-upgrades or apt filtering:
- name: Install security updates only
ansible.builtin.apt:
upgrade: safe
update_cache: true
environment:
APT_LISTBUGS_FRONTEND: none
DEBIAN_FRONTEND: noninteractive
# Alternative: use unattended-upgrades
- name: Ensure unattended-upgrades is installed
ansible.builtin.apt:
name: unattended-upgrades
state: present
- name: Run security updates
ansible.builtin.command: unattended-upgrade --dry-run
register: security_updates
changed_when: false
- name: Apply security updates
ansible.builtin.command: unattended-upgrade
when: "'Packages that will be upgraded' in security_updates.stdout"
Preventing Interactive Prompts
Some package updates prompt for user input. Prevent this:
- name: Non-interactive upgrade
ansible.builtin.apt:
upgrade: dist
update_cache: true
dpkg_options: "force-confold,force-confdef"
environment:
DEBIAN_FRONTEND: noninteractive
NEEDRESTART_MODE: a
| dpkg Option | Description |
|---|---|
force-confold | Keep existing config files |
force-confdef | Use default for new config options |
force-confnew | Always use package's config file |
Updating Specific Package Sets
# Update security-critical packages
- name: Update critical packages
ansible.builtin.apt:
name:
- openssl
- libssl3
- openssh-server
- openssh-client
- linux-image-generic
state: latest
update_cache: true
# Pin a package version
- name: Install specific version
ansible.builtin.apt:
name: nginx=1.24.0-1~jammy
state: present
Monitoring Update Results
- name: Upgrade packages
ansible.builtin.apt:
upgrade: safe
update_cache: true
register: apt_output
- name: Show upgrade summary
ansible.builtin.debug:
msg: |
Changed: {{ apt_output.changed }}
Packages upgraded: {{ apt_output.stdout_lines | select('match', '^Inst') | list | length }}
when: apt_output.changed
Scheduling with Cron
Automate regular updates:
- name: Schedule weekly security updates
ansible.builtin.cron:
name: "Weekly apt security update"
weekday: "0" # Sunday
hour: "3"
minute: "0"
job: "DEBIAN_FRONTEND=noninteractive apt-get -y --only-upgrade install $(apt-get -s upgrade 2>/dev/null | awk '/^Inst.*security/ {print $2}') > /var/log/auto-update.log 2>&1"
Best Practices
- Always use
serialin production — never update all servers simultaneously - Start with a canary — use
serial: [1, "25%", "50%"]to detect issues early - Set
max_fail_percentage: 0— stop immediately if any server fails - Handle reboots — check
/var/run/reboot-requiredafter updates - Use
cache_valid_time— avoid redundantapt-get updatecalls - Set
DEBIAN_FRONTEND: noninteractive— prevent prompts from hanging automation - Use
dpkg_options: force-confold— keep existing config files during upgrades - Clean up after updates —
autoremoveandautocleanto free disk space - Test upgrades in staging first — never roll out untested updates to production
- Log everything — register results and store in audit logs
Related Articles
- Ansible apt Module Guide
- Ansible serial Strategy Guide
- Ansible Reboot Module Guide
- Ansible cron Module Guide
Conclusion
Rolling updates with the Ansible apt module give you controlled, reproducible package management across your Debian/Ubuntu fleet. Use serial for batch processing, handle reboots automatically, set DEBIAN_FRONTEND: noninteractive to prevent prompt hang-ups, and always clean up with autoremove. Combined with health checks and canary deployments, this approach ensures zero-downtime updates at scale.