Skip to content

Instantly share code, notes, and snippets.

@yorickdowne
Last active May 13, 2026 07:03
Show Gist options
  • Select an option

  • Save yorickdowne/057456381eb5d667ffd08432e17e0543 to your computer and use it in GitHub Desktop.

Select an option

Save yorickdowne/057456381eb5d667ffd08432e17e0543 to your computer and use it in GitHub Desktop.
Debian 14 forky upgrade

Debian 14 "Forky"

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 find command, 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 .list Debian repos to deb822 .sources format

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 update after 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

Automated by Ansible

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment