Lightweight Debian installation
Installing a lightweight and minimal Debian system
It has often been said that some distributions are bloated or not as minimal as others like Arch, Gentoo, Void or OpenBSD.
Even though most major distributions, either BSD or Linux, can’t really be considered bloated, it can be argued that some desktop-oriented setups or flavors are far from minimal in their default configurations.
Having said that, nothing really stops a user from making a bare bones install that will take very few resources. This principle applies to most major distributions, which often provide minimal installation processes where the system can be tailored to the user’s needs.
In this post, we will try to get a lightweight system using Debian’s readily minimal installation options.
Installing the base system
Doing a netboot install
The easiest way to get a minimal Debian installation is to use the mini.iso
file, commonly known as netboot (a portmanteau of network boot). We
can download the latest ISO from Debian’s website:
wget https://deb.debian.org/debian/dists/stretch/main/installer-amd64/current/images/netboot/mini.iso
We will be using QEMU to do the installation, so we pass both the ISO
file and the destination device (e.g. /dev/sdi
) as arguments to the
executable:
qemu-system-x86_64 -m 256 -drive file=mini.iso,media=cdrom -drive file=/dev/sdi,format=raw,cache=none
From here on we can use most of the defaults. We just have to make sure nothing is selected during the Software selection step of the installation process:
Using debootstrap
An alternative option is to use the package debootstrap to create a bare installation. To install the necessary packages in our local system, we run:
sudo apt install debootstrap
Since there is no installer to help us create the partitions, we must create them manually. We can use GParted, fdisk or any other partitioning tool for this:
printf 'o\nn\np\n1\n\n\nw\n' | sudo fdisk /dev/sdi
We must then create an ext4 file system with the command:
sudo mkfs.ext4 /dev/sdi1
mke2fs 1.43.4 (31-Jan-2017)
Creating filesystem with 61049389 4k blocks and 15269888 inodes
Filesystem UUID: 60bb0786-b320-4285-abc0-95efce9ac10b
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624, 11239424, 20480000, 23887872
Allocating group tables: done
Writing inode tables: done
Creating journal (262144 blocks): done
Writing superblocks and filesystem accounting information: done
Now, let’s mount the partition:
sudo mount /dev/sdi1 /mnt/debian
And use debootstrap to copy the base system:
sudo debootstrap --arch amd64 stretch /mnt/debian http://deb.debian.org/debian
This will take a few minutes since it has to download the required packages and copy them on to the disk.
Configuring the system
Now we will chroot into our newly created system to apply some finishing touches. But before doing this, we need to recreate some basic file system hierarchy:
sudo mount -t proc /proc /mnt/debian/proc && \
sudo mount --rbind --make-rslave /dev /mnt/debian/dev && \
sudo mount --rbind --make-rslave /sys /mnt/debian/sys && \
sudo mount --rbind --make-rslave /run /mnt/debian/run
Now we can safely use chroot:
sudo chroot /mnt/debian /bin/bash
First of all, let’s change the hostname:
echo 'debianlight' > /etc/hostname && \
echo -e '127.0.1.1\tdebianlight' >> /etc/hosts
By default, /etc/fstab
is empty, so we must add the disk with the UUID of
the file system we created earlier. We can use the command blkid
to see it:
blkid
/dev/sdi1: UUID="60bb0786-b320-4285-abc0-95efce9ac10b" TYPE="ext4" PARTUUID="17a30f04-01"
So let’s create the appropriate entry:
cat <<- 'EOF' > /etc/fstab
UUID=60bb0786-b320-4285-abc0-95efce9ac10b / ext4 defaults 0 0
EOF
For Debian’s stable release, updates to stuff like virus scanners or timezone
data are delivered via the updates repository. Therefore, it’s a good
idea to add it to our sources.list
:
cat <<- 'EOF' >> /etc/apt/sources.list
deb http://deb.debian.org/debian stretch-updates main
EOF
We should also make sure to enable the security updates, especially if this system is to be online. For this, we need to add the repository for Debian’s security team:
cat <<- 'EOF' >> /etc/apt/sources.list
deb http://security.debian.org/debian-security stretch/updates main
EOF
Let’s get our system up to date:
apt update && apt upgrade --no-install-recommends
A debootstrap installation is mainly used for chroots or containers, therefore it’s missing a few fundamental packages. To have a bootable system, we must install them before we boot the machine for the first time.
First let’s install and configure the locales to avoid some annoying error messages:
apt install --no-install-recommends locales && dpkg-reconfigure locales
Since no timezone is configured, the time and date may be reported erroneously. Let’s fix that:
dpkg-reconfigure tzdata
Of course, we shouldn’t forget about installing a kernel:
apt install --no-install-recommends linux-image-amd64
Also, we have to install a boot loader on our disk (e.g. /dev/sdi
) so the
system can be booted:
apt install --no-install-recommends grub-pc && update-grub
And replace all entries in grub.cfg
with the UUID for our disk:
sed -i 's,root=/dev/sdi[0-9],root=UUID=60bb0786-b320-4285-abc0-95efce9ac10b,' /boot/grub/grub.cfg
At the moment, we are using our system’s network connection inside the chroot,
but this won’t be available once we boot this new system by itself. We need to
add a configuration for our network card in the
/etc/network/interfaces.d
directory. Since we are going to test the
installation with QEMU, we can use the default interface’s name:
cat <<- 'EOF' > /etc/network/interfaces.d/ens3
allow-hotplug ens3
iface ens3 inet dhcp
EOF
In order to login as root, we’ll need to set a password:
passwd
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Finally, let’s clean up and exit the chroot so we can boot into our new system:
apt clean; exit
Although first we should cleanly unmount the file hierarchy we previously set up:
sudo umount -R /mnt/debian
Booting the system
Now, let’s boot up with QEMU:
qemu-system-x86_64 -m 256 -drive file=/dev/sdi,format=raw,cache=none -net user,hostfwd=tcp::2222-:22 -net nic
Manually entering commands in QEMU’s console is not very efficient (it’s not possible to copy and paste), so it’s always a good idea to install SSH so we can access the system remotely. After logging in as root, we can install it:
apt install --no-install-recommends ssh
We must also enable root access by modifying /etc/ssh/sshd_config
:
cat <<- 'EOF' >> /etc/ssh/sshd_config
PermitRootLogin yes
EOF
Once we have rebooted the virtual machine, we should be able to connect from our own machine using SSH:
ssh -p 2222 root@localhost
System resources usage
Let’s see how much of the system’s resources we are using. For instance, for the netboot install:
df -h --output=source,used,target /
Filesystem Used Mounted on
/dev/sda1 635M /
free -ht | grep ^Total
Total: 240M 26M 168M
dpkg -l | grep ^ii | wc -l
217
And for the debootstrap install:
df -h --output=source,used,target /
Filesystem Used Mounted on
/dev/sda1 579M /
free -ht | grep ^Total
Total: 240M 26M 169M
dpkg -l | grep ^ii | wc -l
191
Not bad! But we can do better.
Avoiding unnecessary packages
Since we are trying to make this system as minimal as possible, we should make
sure only the required packages are installed without having to provide the
--no-install-recommends
option every time:
cat <<- 'EOF' >> /etc/apt/apt.conf.d/99local
APT::Install-Suggests "0";
APT::Install-Recommends "0";
EOF
Removing some fluff
Now, we can trim some disk space by deleting unused locales. We can just use a one-liner to do this:
find /usr/share/locale -mindepth 1 -maxdepth 1 ! -name 'en*' -exec rm -r {} \;
To prevent packages from installing unwanted locales, we can force dpkg to ignore them:
cat <<- 'EOF' > /etc/dpkg/dpkg.cfg.d/01_nolocales
path-exclude /usr/share/locale/*
path-include /usr/share/locale/en*
EOF
The same thing can be done for documentation files:
find /usr/share/doc -depth -type f ! -name copyright -delete
find /usr/share/doc -empty -delete
rm -rf /usr/share/man /usr/share/groff /usr/share/info /usr/share/lintian /usr/share/linda /var/cache/man
And to prevent them from being installed at all:
cat <<- 'EOF' > /etc/dpkg/dpkg.cfg.d/01_nodocs
path-exclude /usr/share/doc/*
path-include /usr/share/doc/*/copyright
path-exclude /usr/share/man/*
path-exclude /usr/share/groff/*
path-exclude /usr/share/info/*
path-exclude /usr/share/lintian/*
path-exclude /usr/share/linda/*
EOF
We can even get more space by removing some unnecessary packages:
apt purge --auto-remove apt-listchanges aptitude aspell* at avahi-autoipd avahi-daemon bc bluetooth debconf-i18n debian-faq* doc-debian eject exim4-base groff iamerican ibritish info installation-report ispell* krb5-locales logrotate manpages modemmanager nano os-prober pcscd ppp popularity-contest reportbug rsyslog util-linux-locales wamerican
To remove old logs, we can run the following command:
find /var/log -type f -cmin +10 -delete
Final comparison
What does our system looks like after all these improvements? Let’s see how the netboot install fares:
df -h --output=source,used,target /
Filesystem Used Mounted on
/dev/sda1 544M /
free -ht | grep ^Total
Total: 240M 25M 170M
dpkg -l | grep ^ii | wc -l
202
And for the debootstrap install:
df -h --output=source,used,target /
Filesystem Used Mounted on
/dev/sda1 503M /
free -ht | grep ^Total
Total: 240M 25M 171M
dpkg -l | grep ^ii | wc -l
187
We can see that the netboot install adds some extra packages that we could easily remove, although the improvement would be marginal:
apt purge --auto-remove busybox discover kbd keyboard-configuration laptop-detect pciutils task-english
Other tweaks
Removing systemd
If we are no fans of systemd and aren’t using any of its features, we can remove it and install a different init system in its place. For this example, we will install SysV:
apt install --purge --auto-remove --no-install-recommends sysvinit-core
Then, let’s create an inittab
file:
cp /usr/share/sysvinit/inittab /etc/inittab
After a reboot using the new init system, we can purge systemd:
apt purge --auto-remove systemd libpam-systemd
To avoid installing any systemd package in the future, we will configure APT accordingly:
cat <<- 'EOF' >> /etc/apt/preferences.d/nosystemd
Package: libsystemd0
Pin: release *
Pin-Priority: 500
Package: *systemd*
Pin: release *
Pin-Priority: -1
EOF
Without systemd, we get a slight improvement on memory usage and we also get below the 500MB mark of disk usage:
df -h --output=source,used,target /
Filesystem Used Mounted on
/dev/sda1 495M /
free -ht | grep ^Total
Total: 240M 21M 96M
Using dropbear
dropbear is a lightweight SSH server designed for small memory environments. We can install it by running:
apt install --no-install-recommends dropbear-run
We must also enable the service so it starts during boot:
sed -i 's,^NO_START=1,NO_START=0,' /etc/default/dropbear
Now, we can remove OpenSSH:
apt purge --auto-remove ssh
Going really minimal
If we are really short on disk space, we can run debootstrap with the
--variant=minbase
option:
sudo debootstrap --arch amd64 --variant=minbase stretch /mnt/debian http://deb.debian.org/debian
This option, according to debootstrap’s manual page, only installs the essential packages:
Currently, the variants supported are minbase, which only includes essential packages and apt; buildd, which installs the build-essential packages into TARGET; and fakechroot, which installs the packages without root privileges. The default, with no --variant=X argument, is to create a base Debian installation in TARGET.
– Manpages
Along with the usual debootstrap configuration, in order to have a functional environment, we need to install an init system (such as SysV) and some network tools so we can connect to the network:
apt install --no-install-recommends ifupdown iproute2 isc-dhcp-client netbase
In the end, we should be able to shave off a few megabytes of disk space by having less packages than the default debootstrap variant, and also use less memory:
df -h --output=source,used,target /
Filesystem Used Mounted on
/dev/sda1 453M /
free -ht | grep ^Total
Total: 240M 19M 182M
dpkg -l | grep ^ii | wc -l
118