This is a placeholder gist for upgrade to Debian 14 "Forky" in summer 2027. Using it before this release date is risky. Here be dragons.
To start, read the official release notes.
If your install fits into "vanilla Debian plus maybe a handful of 3rd-party repos", then this guide for a simple upgrade to Debian 14 "forky" from Debian 13 "trixie" can be helpful. 3rd-party repos are handled with a find command.
If you are on a fork of Debian such as RasPI OS, use their instructions, not this gist.
Note upgrade is only supported from Debian 13 to Debian 14. If you are on Debian 12, upgrade to Debian 13 first. Then once on Debian 13, you can upgrade to Debian 14.
This guide is only for the OS itself. Applications are as plentiful as sand on the beach, and they may all require additional steps. Plan for that.
- Check free disk space
df -h
5 GiB free is a conservative amount. sudo apt clean and sudo apt autoremove can be used to free some disk space.
On a server with only docker installed, even 1 GiB free was sufficient. Do err on the side of caution here, however.
- Identify all repos that may need to be updated, including 3rd-party. They'll be changed with a
findcommand, below.
ls /etc/apt/sources.list.d
- Update current distribution
sudo apt update && sudo apt full-upgrade
If this brought in a new kernel, sudo reboot - otherwise continue
- Optional: Start a screen session
So that an SSH disconnect doesn't stop your upgrade halfway through, you can run in screen:
sudo apt install -y screen && screen
- Change old-style
.listDebian repos to deb822.sourcesformat
Run sudo apt modernize-sources, and verify this worked. If any 3rd-party repos did not automatically move to .sources format,
re-create them manually in deb822 format.
- Change all repos in sources.list.d to forky, from trixie
This assumes the repos have forky versions. Run
sudo apt updateafter the change to forky to confirm, and deal with any repos that aren't available in forky.
sudo find /etc/apt/sources.list.d -type f -name '*.sources' -exec sed -i 's/trixie/forky/g' {} \;
Run sudo apt update and if it fails on some repos because they don't have forky versions, disable them for now by adding Enabled: no to the bottom of the .sources file, then test again.
- Update Debian
For the following, say Yes to restarting services, and keep existing config files when prompted.
sudo apt update && sudo apt full-upgrade
- If you use mdadm, stop and check EFI
Having more than one ESP (EFI System Partition) is an unusual setup. If you are certain you have only one ESP, move on to the next step.
If you have multiple ESPs for this copy of Debian, make sure they are all upgraded. Debian 14 uses grub 2.14.
- Reboot - If you use mdadm, only proceed if you know EFI is OK
sudo reboot
- Clean up old repos
sudo apt autoremove && sudo apt clean
Caution: This Ansible playbook does not check EFI has been updated on all disks, on an mdadm setup with multiple ESPs. Only use on machines with only one ESP, or adjust this playbook to take care of upgrading all EFI partitions.
Config ansible.cfg:
[defaults]
interpreter_python = /usr/bin/python3
Playbook forky.yml:
---
- name: Upgrade to Debian forky
hosts: all
serial: 1
gather_facts: false
roles:
- base/upgrade_forky
Role base/upgrade_forky/tasks/main.yml:
---
- name: Get distribution version
setup:
filter: ansible_distribution*
- name: Skip if not Debian 13
meta: end_host
when: ansible_distribution != 'Debian' or ansible_distribution_major_version != '13'
- name: apt clean
apt:
clean: yes
become: yes
- name: Get filesystem facts
setup:
filter: ansible_mounts
- name: Fail if free space on / is below 5 GiB
ansible.builtin.assert:
that:
- item.size_available > (5 * 1024 * 1024 * 1024)
fail_msg: "Free disk space on {{ item.mount }} is below 5 GiB"
loop: "{{ ansible_mounts }}"
when: item.mount == "/"
- name: All apt packages up to date
apt:
upgrade: dist
update_cache: yes
become: yes
- name: apt autoremove
apt:
autoremove: yes
become: yes
- name: apt clean
apt:
clean: yes
become: yes
- name: Check if reboot required
ansible.builtin.stat:
path: /run/reboot-required
get_checksum: no
register: reboot_required_file
- name: Reboot if required
ansible.builtin.reboot:
msg: "Reboot initiated by Ansible"
connect_timeout: 5
reboot_timeout: 600
pre_reboot_delay: 0
post_reboot_delay: 60
test_command: whoami
when: reboot_required_file.stat.exists
become: true
- name: Modernize apt sources
ansible.builtin.command:
cmd: apt -y modernize-sources
become: yes
- name: Find all repos
ansible.builtin.find:
paths: /etc/apt/sources.list.d
patterns: '*.sources'
recurse: no
register: all_repos
- name: Switch repos from trixie to forky
ansible.builtin.replace:
path: "{{ item.path }}"
regexp: 'trixie'
replace: 'forky'
loop: "{{ all_repos.files }}"
loop_control:
label: "{{ item.path }}"
become: yes
- name: Use apt to move to forky
apt:
upgrade: dist
update_cache: yes
become: yes
- name: Get distribution version
setup:
filter: ansible_distribution*
- name: Fail if not Debian 14
assert:
that:
- ansible_distribution_major_version == '14'
fail_msg: "Upgrade to Debian 14 failed"
- name: apt autoremove
apt:
autoremove: yes
become: yes
- name: apt clean
apt:
clean: yes
become: yes
- name: Reboot on forky
ansible.builtin.reboot:
msg: "Reboot initiated by Ansible"
connect_timeout: 5
reboot_timeout: 600
pre_reboot_delay: 0
post_reboot_delay: 60
test_command: whoami
become: yes
- name: Pause for 5 minutes for staggered upgrades
pause:
minutes: 5