Skip to main content

Debian 13 Trixie Upgrade on Proxmox

·823 words·4 mins
Stanislav Cherkasov
Author
Stanislav Cherkasov
{DevOps,DevSecOps,Platform} Engineer
Table of Contents
homelab - This article is part of a series.
Part : This Article

This is my personal checklist for upgrading a Proxmox host from Debian Bookworm to Debian Trixie. I keep it short and repeatable.

Step 0: Backup
#

personally I prefer lvm snapshot. Soon I am going to automate it via ansible for my homelab, but right at the moment bash script is here

Step 1: Switch APT sources to Trixie and pre-download packages
#

chattr -i /etc/apt/sources.list /etc/apt/sources.list.d/* \
  ; sed -i 's/bookworm/trixie/g' /etc/apt/sources.list \
  ; sed -i 's/bookworm/trixie/g' /etc/apt/sources.list.d/* \
  ; apt clean \
  ; apt update \
  ; apt-get dist-upgrade --download-only -y

yes, I use chattr for sources and even for resolv.conf

Step 2: Run non-interactive dist-upgrade and reboot
#

DEBIAN_FRONTEND=noninteractive apt \
  -o Dpkg::Options::="--force-confdef" \
  -o Dpkg::Options::="--force-confold" -y dist-upgrade \
  ; apt clean \
  ; reboot

Step 3: Clean Proxmox repository entries and do the final upgrade pass
#

Run this block after the first reboot. I apply repository cleanup conditionally:

  • keep pve-enterprise if the node has an active enterprise subscription
  • remove Ceph repository files only if the node is not in a Ceph cluster
# only for no-subscription hosts
rm -f /etc/apt/sources.list.d/pve-enterprise.*

# only if this node is not part of a Ceph cluster
#rm -f /etc/apt/sources.list.d/ceph*

apt clean
apt update
apt dist-upgrade -y
apt autoremove -y
reboot

Post-upgrade checks
#

cat /etc/debian_version
pveversion -v
apt update
apt full-upgrade -y
systemctl --failed

If systemctl --failed returns no failed units and apt update has no dependency errors, I treat the upgrade as complete.

Optional automation with Ansible
#

For repeated upgrades across multiple hosts, I keep a deb822-based Ansible variant. It mirrors the same idea as the manual flow above: normalize repository files, then apply consistent Debian and Proxmox sources.

Important detail for major release migration: this variable set ties suites to ansible_distribution_release. For a Bookworm to Trixie migration run, I use an explicit target release variable in inventory and map suites to that target in the role.

Variables example (Debian and Proxmox repos)
#

your_role_here_repo_name_debian:       "debian"
your_role_here_repo_suites_debian:     "{{ ansible_distribution_release }}"
your_role_here_repo_components_debian: "main contrib non-free-firmware"
your_role_here_repo_uris_debian:
  - "http://deb.debian.org/debian/"
  # in case you have nexus
  #- "https://{{ nexus_fqdn }}/repository/debian/"

# debian-updates
your_role_here_repo_name_debian_updates:       "{{ your_role_here_repo_name_debian }}-updates"
your_role_here_repo_suites_debian_updates:     "{{ your_role_here_repo_suites_debian }}-updates"
your_role_here_repo_components_debian_updates: "{{ your_role_here_repo_components_debian }}"
your_role_here_repo_uris_debian_updates:       "{{ your_role_here_repo_uris_debian }}"

# debian-security
your_role_here_repo_name_debian_security:       "{{ your_role_here_repo_name_debian }}-security"
your_role_here_repo_suites_debian_security:     "{{ your_role_here_repo_suites_debian }}-security"
your_role_here_repo_components_debian_security: "{{ your_role_here_repo_components_debian }}"
your_role_here_repo_uris_debian_security:
  - "http://security.debian.org/debian-security/"
  # in case you have nexus
  #- "https://{{ nexus_fqdn }}/repository/debian-security/"

# pve
your_role_here_repo_pve_name:       "pve-install-repo"
your_role_here_repo_pve_gpg:        "/usr/share/keyrings/proxmox-archive-keyring.gpg"
your_role_here_repo_pve_suites:     "{{ ansible_distribution_release }}"
your_role_here_repo_pve_components: "pve-no-subscription"
your_role_here_repo_pve_uris:
  - "http://download.proxmox.com/debian/pve/"
  # in case you have nexus
  #- "https://{{ nexus_fqdn }}/repository/pve/"

# pbs
your_role_here_repo_pbs_name:       "pbs-no-subscribtion"
your_role_here_repo_pbs_gpg:        "/usr/share/keyrings/proxmox-archive-keyring.gpg"
your_role_here_repo_pbs_suites:     "{{ ansible_distribution_release }}"
your_role_here_repo_pbs_components: "pbs-no-subscription"
your_role_here_repo_pbs_uris:
  - "http://download.proxmox.com/debian/pbs/"
  # in case you have nexus
  #- "https://{{ nexus_fqdn }}/repository/pbs/"

Task example (deb822 migration and repo setup)
#

- name: cleanup old sources.list
  copy:
    dest: '/etc/apt/sources.list'
    content: "#"

- name: Debian | Remove obsolete repo files
  when:
    - ansible_distribution == "Debian"
  file:
    state: absent
    path: "{{ item }}"
  loop: "{{ your_role_here_repo_obsolete }}"

- name: configure repo | debian
  when:
    - ansible_distribution == 'Debian'
  ansible.builtin.deb822_repository:
    types:         deb
    architectures: amd64
    signed_by:     "{{ your_role_here_repo_gpg }}"
    name:          "{{ your_role_here_repo_name_debian }}"
    suites:        '{{ your_role_here_repo_suites_debian }}'
    uris:          "{{ your_role_here_repo_uris_debian }}"
    components:    "{{ your_role_here_repo_components_debian }}"

- name: configure repo | debian-updates
  when:
    - ansible_distribution == 'Debian'
  ansible.builtin.deb822_repository:
    types:         deb
    architectures: amd64
    signed_by:     "{{ your_role_here_repo_gpg }}"
    name:          "{{ your_role_here_repo_name_debian_updates }}"
    suites:        '{{ your_role_here_repo_suites_debian_updates }}'
    uris:          "{{ your_role_here_repo_uris_debian_updates }}"
    components:    "{{ your_role_here_repo_components_debian_updates }}"

- name: configure repo | debian-security
  when:
    - ansible_distribution == 'Debian'
  ansible.builtin.deb822_repository:
    types:         deb
    architectures: amd64
    signed_by:     "{{ your_role_here_repo_gpg }}"
    name:          "{{ your_role_here_repo_name_debian_security }}"
    suites:        '{{ your_role_here_repo_suites_debian_security }}'
    uris:          "{{ your_role_here_repo_uris_debian_security }}"
    components:    "{{ your_role_here_repo_components_debian_security }}"

- name: Check if /etc/pve exists and is a directory
  stat:
    path: /etc/pve
  register: pve_stat

- name: configure repos | pve
  when:
    - pve_stat.stat.exists
    - pve_stat.stat.isdir
    - ansible_distribution == 'Debian'
  ansible.builtin.deb822_repository:
    types:         deb
    architectures: amd64
    signed_by:     "{{ your_role_here_repo_pve_gpg }}"
    name:          "{{ your_role_here_repo_pve_name }}"
    suites:        '{{ your_role_here_repo_pve_suites }}'
    uris:          "{{ your_role_here_repo_pve_uris }}"
    components:    "{{ your_role_here_repo_pve_components }}"

- name: Check if /etc/proxmox-backup exists and is a directory
  stat:
    path: /etc/proxmox-backup
  register: pbs_stat

- name: configure repos | pbs
  when:
    - pbs_stat.stat.exists
    - pbs_stat.stat.isdir
    - ansible_distribution == 'Debian'
    - "'pbs' in group_names"
  ansible.builtin.deb822_repository:
    types:         deb
    architectures: amd64
    signed_by:     "{{ your_role_here_repo_pbs_gpg }}"
    name:          "{{ your_role_here_repo_pbs_name }}"
    suites:        '{{ your_role_here_repo_pbs_suites }}'
    uris:          "{{ your_role_here_repo_pbs_uris }}"
    components:    "{{ your_role_here_repo_pbs_components }}"

Notes
#

I use the manual commands for one host and emergency maintenance windows. I use the Ansible path when I need the same repository state on multiple Debian or Proxmox nodes.

Why it matters
#

This note keeps upgrades repeatable and low stress. The command order is fixed, conditions are explicit, and I can reuse the same process on the next host without rebuilding it from memory.

References
#

homelab - This article is part of a series.
Part : This Article