The (ongoing) quest for 1 second Linux boot to desktop

2026-01-25 tinkering software linux alpine boot-time-optimization

Micro VMs are becoming more popular as a more secure alternative to containers, but VMs traditionally suffered from long boot times that containers typically haven’t. Boot time optimizations make VMs more accessible for ephemeral loads that need to spin up quickly.

That’s not why I’m interested in fast boot times.

Motivation🔗

My typical workflow for video gaming is sitting down on the couch, firing up my 2017 Dell XPS, and starting to stream the game of choice via Gigabit Ethernet from my racked rig that performs much better than this old laptop.

The “firing up my 2017 Dell XPS” part is annoyingly slow, because I begrudgingly use Windows 10 to avoid Steam on Linux breaking on a regular basis, because the Nvidia driver breaking on every other update. Steam is used to start and stream the game remotely. Yes, this would be a totally different story if I had gotten a machine with an AMD GPU back then, but here we are.

On such an “old” machine, Windows 10 takes half a minute to login screen, and more than a minute after login until Steam is finally ready to stream, with all the other post-login tasks that contend for CPU time.

Side note: Although being kept from muh games fired up my motivation, I face the same frustration when booting up the Linux install to do something productive.

Slow startup could also be mitigated by suspending the OS rather than shutting down, but this particular device has the habit of draining the battery quickly while suspended, so I often end up booting a fully drained device from scratch that I suspended a day or two before.

Busy adults don’t have time for that, so let’s spend hours to save seconds!

The plan🔗

The root of the problem is that I have specific needs that I try to satisfy with a general purpose operating system. As such, the OS does way more than is necessary for me to reach my goal (gaming, tinkering). These additional activities cause delays and compete for resources with the things I actually care about.

These high level principles should get me on the right track:

  • Start lean - stay lean.
  • Strip out everything that’s not needed.
  • Delay everything that’s optional.
  • Challenge assumptions.

The goal is to minimize time from power button press to having a usable, responsive desktop.

Starting lean🔗

I knew Alpine Linux from its container images that are only a few MiB in size. It’s also a fully-capable bare metal operating system that makes lightweight choices in all the right places:

  • OpenRC instead of SystemD.
  • musl instead of glibc.
  • busybox instead of GNU coreutils.

To be fair, the latter two won’t help much with boot time, and SystemD with its aggressive concurrency style might have benefits, but it all comes with some bloat that is bound to affect boot times.

Let’s go with Alpine.

UEFI🔗

Most power on self test (POST) routines can be disabled to save time (sometimes summarized as “fast boot”). I’ll be able to tell something is wrong if something is broken, so I can turn those back on when I have some hardware issues.

Bootloader🔗

GRUB could be configured to be faster, but why make something smaller if something small that is perfectly fine already exists? I can use UEFI for operating system choice and use Syslinux to boot straight from UEFI into the OS.

A unified Kernel image could make this even faster - let’s keep that on the list for further improvements.

Kernel image and command line🔗

We can optimize Kernel loading times by removing everything we don’t need from initramfs, and making sure all the modules we need are baked into it so they don’t have to cause delays due to loading at boot.

What this looks like in detail depends on specific hardware configuration and use case. For my 2017 XPS, /etc/mkinitfs/mkinitfs.conf looks like this:

features="base kms xfs ahci nvme"
add_modules="nouveau i915 xhci_pci i2c_i801 i2c_hid hid_multitouch dell_smbios dell_wmi dell_wmi_descriptor"

It's important to add the driver of the root file system to `features`. Missing modules can otherwise always be loaded later, but only if the file is accessible. I briefly broke boot by forgetting to add `xfs` and `nvme`.

The boot command line itself isn’t very exciting - just minimal:

modules=sd-mod,usb-storage,xfs,nvme quiet nowatchdog loglevel=0 rootfstype=xfs

I didn’t go as far as compiling the kernel myself, but that might still squeeze a bit more out of this.

Nvidia detail🔗

This 2017 XPS has an Nvidia GPU. The FOSS drive Nouveau is still mid in 2026, but the Nvidia driver wastes lot of time during boot for modesetting. Let’s make sure to ban it in /etc/modprobe.d/deny-nvidia.conf:

blacklist nvidia
blacklist nvidia_drm
blacklist nvidia_modeset
blacklist nvidia_uvm

The old adage holds true.

Services🔗

OpenRC only starts one service at a time by default. This can be fixed in /etc/rc.conf (at the cost of potentially jumbled log output), so only dependent services will start in sequence:

rc_parallel="YES"

Do we even need a login screen?🔗

I said “challenge assumptions”, right?

This is my personal machine, so it’s essentially a single user system. Nobody else will ever log in interactively, so I can boot straight to desktop.

Sway seems to be a good, lean, tiling window manager - I had been itching to try it for a while, so let’s boot straight to Sway desktop and lock it right away for security.

The very minimum Sway setup requires running setup-wayland-base, making sure appropriate graphics drivers are installed, and adding greetd, seatd, elogind, and dbus packages to get the whole session management life cycle right. This also required adding these services to the default runlevel:

  • dbus
  • elogind
  • greetd (depending on seatd, dbus, elogind).
  • seatd

elogind needs to be added as a PAM plugin to enable auto-login in /etc/pam.d/system-local-login:

session optional pam_elogind.so

auth include system-local-login
account include system-local-login
password include system-local-login
session include system-local-login

The auto login part can be achieved by configuring the greeter (greetd), which would normally serve the role of login screen, so it would automatically start a default session based on a command. In /etc/greetd/config.toml:

[terminal]
vt=1

[default_session]
command="dbus-run-session -- /usr/local/bin/auto-login"
user="<user>"

The referenced /usr/local/bin/auto-login script takes care of bootstrapping a minimal Sway session:

#!/bin/sh
# Start a Sway session and lock immediately with swaylock-effects.

export XDG_CURRENT_DESKTOP=sway
export XDG_SESSION_DESKTOP=sway
export XDG_SESSION_TYPE=wayland
# Variables below are the result of quirk troubleshooting
# and maybe not needed by others.
export MOZ_ENABLE_WAYLAND=1
export SDL_VIDEODRIVER=wayland
export QT_QPA_PLATFORM=wayland
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
export _JAVA_AWT_WM_NONREPARENTING=1
export WLR_DRM_NO_ATOMIC=1
export WLR_DRM_NO_MODIFIERS=1

# Start a user bus if none exists (keeps things like notifications/portals happy):
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ] && command -v dbus-launch >/dev/null 2>&1; then
  eval "$(dbus-launch --sh-syntax)"
fi

# Sway login config that loads shared settings and executes the login screen
# configured in the $lock Sway config variable.
TMP_CONFIG="$(mktemp)"
cat > "$TMP_CONFIG" <<'EOF'
# Pull in the system and user configs first (order matters)
include /etc/sway/early.conf
include /etc/sway/config
include ~/.config/sway/config

exec_always --no-startup-id $lock --image $wallpaper
EOF

# Clean up the temp file on exit
trap 'rm -f "$TMP_CONFIG"' EXIT

# Run Sway with the initial config
exec sway --config "$TMP_CONFIG" 2>/tmp/sway.log

include /etc/sway/early.conf is very optional - as I’m using a semi-transparent lock screen, I set the wallpaper and start up waybar in this config, so the lock screen implies that there is already a working desktop underneath.

My $lock variable looks like this, but it’s mostly a matter of taste:

set $lock swaylock --daemon --screenshots \
  --effect-pixelate 10 \
  --indicator --indicator-radius 120 --indicator-thickness 10 \
  --clock --datestr "%a %d %b" --timestr "%H:%M"

Rewiring dependencies🔗

Nothing besides the display needs to be ready for interactivity, and he rest can start up while I’m typing my password into the lock screen.

I removed these services from the default runlevel, so they’d only be executed if they are a dependency of nothing else:

  • agetty.tty1 - deferred start as dependency.
  • bluetooth - not using it with this machine.
  • chronyd - NTP can be deferred.
  • cupsd - not using this machine for printing.
  • NetworkManager - I set up all the connections I need with setup-networking.

I went through the service definitions of greetd, elogind, and seatd as well to make sure that hey don’t depend on runlevel default, but already stat up after localmount to exploit parallel service start better.

Side quest: Getting rid of the Steam startup delay🔗

This isn’t strictly about improving boot time, but it cuts down time to being in-game.

Steam isn’t exactly quick to start, always checking for updates and occasionally installing them as well. We can shave off some time and, more importantly, save us the trouble of trying to get Steam to work on Alpine if we switch to an alternative streaming solution.

Sunshine and Moonlight are the perfect replacement. Sunshine runs in the background on the gaming machine, and Moonlight acts as the client.

Theoretically, Moonlight could stream directly from some Nvidia streaming feature that’s included in the driver, but I prefer Sunshine, because it provides lots of settings for leaving me in control, which has already proven to be useful when things went wrong after a change in hardware peripherals.

Sunshine runs persistently, and Moonlight starts instantly, so I really am ready to go after typing in my password, without any further wait.

Results🔗

I’m still far off one second boot time for sure, but these improvements already offer nice progress.

Doing a stopwatch measurement of the boot process on my trusty 2017 XPS, I get these timings:

  • Firmware: 7.96 seconds
  • Bootloader: 5.04 seconds
  • Boot: 1.92 seconds 🔥
  • Total: 14.92 seconds

Can’t change much about how long the firmware takes anymore, so the biggest room for improvement is probably Kernel size - the boot loader takes really long to load it.

The amazing part is that time from init to lock screen, which I spent most effort on, is below 2 seconds already! I should try the same optimizations on more current hardware as well to see how it performs there.

Although I’m far from the (very ambitious) goal I set myself, I’m very happy with the result already, so I’ll probably leave it at that for a while. I finally don’t have to boot into Windows anymore on this machine.