Can I remove the older revision package of duplicated snap packages?

While examining the snap packages that are installed in a system, I noticed that some packages have a duplicate, one having an over revision number while the other having a newer revision number. For such duplicated packages, my questions are:

  1. Why are they duplicated?
  2. Can I remove the older package to ensure better disk space management?
  3. How do I remove the older package?

Below are examples of packages that do and do not have duplicates:

$ du -hcs /var/lib/snapd/snaps/*
31M /var/lib/snapd/snaps/2048x_3.snap
286M    /var/lib/snapd/snaps/atom_282.snap
4.0K    /var/lib/snapd/snaps/bare_5.snap
72M /var/lib/snapd/snaps/bitwarden_58.snap
72M /var/lib/snapd/snaps/bitwarden_59.snap
196M    /var/lib/snapd/snaps/blender_1113.snap
214M    /var/lib/snapd/snaps/blender_1237.snap
9.1M    /var/lib/snapd/snaps/canonical-livepatch_119.snap
9.1M    /var/lib/snapd/snaps/canonical-livepatch_126.snap
148M    /var/lib/snapd/snaps/chromium_1854.snap
148M    /var/lib/snapd/snaps/chromium_1864.snap
17M /var/lib/snapd/snaps/chromium-ffmpeg_23.snap
18M /var/lib/snapd/snaps/chromium-ffmpeg_24.snap
....

~$ ls -lh /var/lib/snapd/snaps/
total 12G
-rw------- 2 root root  31M Aug  5 06:23 2048x_3.snap
-rw------- 2 root root 286M Aug  5 08:35 atom_282.snap
-rw------- 2 root root 4.0K Sep 22 18:17 bare_5.snap
-rw------- 1 root root  72M Oct 30 00:20 bitwarden_58.snap
-rw------- 1 root root  72M Dec  9 04:28 bitwarden_59.snap
-rw------- 1 root root 196M Nov 18 04:06 blender_1113.snap
-rw------- 1 root root 214M Dec  4 09:39 blender_1237.snap
-rw------- 2 root root 9.1M Nov 17 21:06 canonical-livepatch_119.snap
-rw------- 2 root root 9.1M Nov 22 22:39 canonical-livepatch_126.snap
-rw------- 1 root root 148M Dec 16 04:28 chromium_1854.snap
-rw------- 1 root root 148M Jan  8 08:33 chromium_1864.snap
-rw------- 1 root root  17M Sep  3 06:29 chromium-ffmpeg_23.snap
-rw------- 2 root root  18M Nov 29 14:23 chromium-ffmpeg_24.snap
....

On the system that I am looking at, the total disk space utilized by /var/lib/snapd/snaps/* is 12,180.248 MB. The disk space of all the duplicated packages(i.e. older revision of the same package) is 4,163.1 MB. In short, the older revision packages currently takes up 34.18% of the 12,180.248 MB. This appears to be a cost to using SNAP apps that I had not realised before.


To answer your questions:

Why are they duplicated?

⇢ They're different revisions (versions), not duplications.

Can I remove the older package to ensure better disk space management?

⇢ Yes. It's your computer, after all.

How do I remove the older package?

You can do this in Terminal like this:

snap remove {snap} --revision={revision}

You can also tell the system how many past versions to limit itself to like this:

sudo snap set system refresh.retain=2

Note: The value must be between 2 and 20, and a number like 2 or 3 is generally recommended to save storage space and allow a rollback in the event of a bad update.

If you would like to list all the snaps and their versions, you can run this command:

snap list --all

Which will give you something like:

Name                 Version                     Rev    Tracking         Publisher   Notes
bare                 1.0                         5      latest/stable    canonical✓  base
canonical-livepatch  10.0.1                      119    latest/stable    canonical✓  disabled
canonical-livepatch  10.1.2                      126    latest/stable    canonical✓  -
core                 16-2.52                     11798  latest/stable    canonical✓  core,disabled
core                 16-2.52.1                   11993  latest/stable    canonical✓  core
core18               20211028                    2253   latest/stable    canonical✓  base
core18               20211015                    2246   latest/stable    canonical✓  base,disabled
core20               20211115                    1242   latest/stable    canonical✓  base,disabled
core20               20211129                    1270   latest/stable    canonical✓  base
gnome-3-28-1804      3.28.0-19-g98f9e67.98f9e67  145    latest/stable    canonical✓  disabled
gnome-3-28-1804      3.28.0-19-g98f9e67.98f9e67  161    latest/stable    canonical✓  -
gnome-3-34-1804      0+git.3556cb3               77     latest/stable/…  canonical✓  -
gnome-3-34-1804      0+git.3556cb3               72     latest/stable/…  canonical✓  disabled
gnome-3-38-2004      0+git.cd626d1               87     latest/stable    canonical✓  -
gnome-3-38-2004      0+git.6ba6040               76     latest/stable    canonical✓  disabled
gtk-common-themes    0.1-52-gb92ac40             1515   latest/stable/…  canonical✓  disabled
gtk-common-themes    0.1-59-g7bca6ae             1519   latest/stable/…  canonical✓  -
snap-store           3.38.0-66-gbd5b8f7          558    latest/stable/…  canonical✓  -
snap-store           3.38.0-64-g23c4c77          547    latest/stable/…  canonical✓  disabled
snapd                2.53.2                      14066  latest/stable    canonical✓  snapd,disabled
snapd                2.53.4                      14295  latest/stable    canonical✓  snapd

Need a Script?

IMPORTANT: You will want to check the output of snap list --all on your computer before continuing, and the following is a script that should not be copy/pasted without sanity checking if you are using a locale that is not en_US.UTF-8.

The Script:

#!/bin/bash
# This script will remove disabled snap revisions.
set -eu

LANG=C snap list --all | awk '/disabled/{print $1, $3}' |
    while read name rev; do
        snap remove "$name" --revision="$rev"
    done

This will run snap list -all and extract the lines that contain the word disabled. This will be different depending on your locale, so check the output of the function first, then update awk '/disabled/ to replace disabled with the label that is found in your output.

Save the script to a file (for example scrub-snaps.sh) and then set it as being executable:

sudo chmod +x scrub-snaps.sh

Now you can run it, remembering to use sudo:

sudo ./scrub-snaps.sh

Note: sudo was not part of the script, but can be added if you prefer to have it in there. Either way, you'll be prompted for a password if required.


Keeping at least one older version of a snap is inherent in the design.

Snap packages were originally designed for a variety of environments where there is often no human admin and/or no way to attach a keyboard and monitor...like phones or IOT devices. Resiliency is a critical requirement for these systems: If an application crashes or an upgrade is corrupted, some form of guaranteed rollback without human intervention is needed. Hence the requirement for at least one older version on-hand.

Folks on classic desktops and servers don't care much about that rollback capability. They like different snap design elements: The secure automatic upgrades that are independent of the OS, the read-only squashfs tamper-prevention, the process confinement, etc.

But it's all a single standard, so you get the rollback capability, too. Even if you think you won't use it.

You cannot "disable" the rollback capability of snaps -- it's not really a "feature" but a key design element.


Building on @matigo answer, I wrote a python script to automate the removal of disabled SNAP packages. It allows a user to visually check the snap packages before proceeding with (or not) the removal process. An example of what the executed script does is shown in the link.

remove_disabled_snap_pkgs.py

#!/bin/python3
''' This python script automates the removal of all disabled SNAP packages in
a system. Doing so helps free up the system's disk space. This outcome can be
significant in the situation where many disabled SNAP packages are retained in
the system.
'''
from subprocess import run, PIPE, CalledProcessError
from pathlib import Path
import sys

# Assumptions
SNAP_PKGS_PATH = Path('/var/lib/snapd/snaps/')
# Also, at a minimum, this directory has at least one xxx.snap file there. 


def snap_list():
    '''Function to execute a bash 'snap list' cmd and returns a Python
    dictionary of info of the ACTIVE SNAPCRAFT pkgs in the system.

    pkgs_dict = {Name : {'Version':'xxx', 'Rev':'xxx', 'Tracking':'xxx',
                         'Publisher':'xxx', 'Notes':'xxx'}
                }
    '''
    try:
        cmd = ['snap', 'list']
        completed = run(cmd, check=True, stdout=PIPE)
    except CalledProcessError as err:
        print('ERROR:', err)
    else:
        headers = completed.stdout.decode('utf-8').splitlines()[0].split()
        pkgs=[line.split() for line in
              completed.stdout.decode('utf-8').splitlines()[1:]]
        pkgs_dict = {}
        for pkg in pkgs:
            pkgs_dict[pkg[0]] = {i:pkg[n+1] for n, i in enumerate(headers[1:])}
        return pkgs_dict


# 1. Get all SNAPCRAFT pkgs in system
all_path = sorted(SNAP_PKGS_PATH.glob('*.snap'))
all_size = sum([p.stat().st_size for p in all_path])

# 2. Get active SNAPCRAFT pkgs in system
active_snap_pkgs = snap_list()
active_path = [SNAP_PKGS_PATH / Path(k+'_'+v['Rev']+'.snap')
               for k, v in active_snap_pkgs.items()]
active_size = sum([p.stat().st_size for p in active_path])

# 3. Display info and instructions in terminal
print(f'ALL (ACTIVE & DISABLED) SNAP PACKAGES IN SYSTEM:')
for n, i in enumerate(all_path):
    size = i.stat().st_size
    if i in active_path:
        print(f'Active\t{size:>12}\t{i}')
    else:
        print(f'      \t{size:>12}\t{i}')

# 4. Show stats on total size of All, Active & Disabled SNAPCRAFT packages 
width = 12
disabled_size = all_size - active_size
print('\nSIZE OF SNAP PACKAGES:')
print(f'1. All      : {all_size:>{width}} bytes')
print(f'2. Active   : {active_size:>{width}} bytes')
print(f'2. Disabled : {disabled_size:>{width}} bytes or '
      f'{(disabled_size/all_size):.2%} of All')

# 5. Make decision to remove or not to remove Disabled SNAPCRAFT packages 
if disabled_size > 0:
    print(f'\nREMOVE ALL DISABLED SNAP PACKAGES? [y/n]')
    while True:
        decision = input()
        if decision in ['y', 'Y', 'yes', 'Yes', 'YES']:
            print('Removal in progress... pls wait')
            for p in all_path:
                if p not in active_path:
                    stem = p.stem
                    bar_index = stem.index('_')
                    name = stem[:bar_index]
                    revision = stem[bar_index+1:]
                    cmd = ['sudo', 'snap', 'remove', name,
                           '--revision='+revision]
                    print(f"\n{' '.join(cmd)}")
                    run(cmd, stdout=sys.stdout, stderr=sys.stderr,
                        encoding='utf8')
            print(f'\nREMOVE ALL DISABLED SNAP PACKAGES? COMPLETED.')
            break
        elif decision in ['n', 'N', 'no', 'No', 'NO']:
            print(f'\nNO REMOVAL IS PERFORMED.')
            break
        else:
            print('Please enter only "y" or "n":')
else:
    print(f'\nNO REMOVAL IS NEEDED.')