This is a complete, working setup. Every decision here — Fluxbox over a DE, Testing over Stable, PipeWire over ALSA, Thunar as the file manager — has a reason. This page walks through the stack and explains each one.
Section 01
A WM setup is not a DE with pieces removed. It's a stack you build deliberately. Every component earns its place. If you can't say why something is running, it probably shouldn't be.
xinit gives you startx, which
is all you need to launch a graphical session from the
terminal. No display manager required.
nm-applet lives in the Fluxbox slit.
You right-click it to join a network. The alternative —
wpa_supplicant and hand-editing
/etc/network/interfaces — works, but NM is
the right balance of lean and livable for a daily driver.
thunar-volman gives you USB automounting.
PCManFM is the lighter alternative if you want to go
further down the rabbit hole.
arandr is a GUI wrapper for xrandr — multi-monitor
setup without memorizing flags. numlockx enables Numlock
at startup. xbacklight lets media keys control screen brightness
via Fluxbox keybindings.
cbatticon is a single-purpose battery tray icon —
nothing else. blueman-applet gives you Bluetooth
device management from the slit. Both are small and focused.
Skip if you're on a desktop.
pavucontrol gives you per-app volume. pasystray
sits in the slit. Media keys use wpctl. See Section 05.
evince opens PDFs. vlc plays everything.
libreoffice if you open Word or Excel files.
xtrlock locks the screen without a screensaver daemon —
runs one command, blanks and locks the screen, done.
startx from a TTY is faster,
uses no background resources, and requires no configuration.
See Section 03.
dunst is the standard minimal notification daemon for WM setups.
I don't run it — most of what I do on this machine doesn't need
desktop notifications. Add it if you do:
sudo apt install dunst and it starts automatically.
Section 02
Debian Testing is a rolling release. Package dependencies shift as packages
migrate from Unstable. If you're not careful, apt installing
something you actually want can pull in a full desktop environment you didn't ask for —
because that DE was listed as a Recommend somewhere in the dependency chain.
Two config files fix this permanently.
By default, apt install installs a package and all its
Recommended packages — not just Required ones. On Debian, "Recommended" is
used generously. A package you want might recommend a DE component, which
recommends a display manager, which pulls in half of Cinnamon.
On Testing specifically, this happens more than on Stable because package relationships are actively being renegotiated as maintainers push updates. A package that was safe to install last month might have a new Recommend today.
The fix: tell APT not to install Recommends or Suggests unless you explicitly ask for them. And pin any specific packages you never want — like Cinnamon — to a priority of -1, meaning "never install this, ever."
Create this file. It tells APT to install only what's explicitly Required — not what's Recommended or Suggested. This is the single most impactful change for keeping a Debian system lean.
If you ever need the Recommends for a specific package, you can still get them:
sudo apt install --install-recommends <package>.
The file sets the default, not a hard lock.
To undo it entirely, just delete the file.
Pin priority -1 means APT will never install this package, even if something depends on it. Apt will error and tell you rather than silently pulling in a DE.
You can verify the pin took effect:
Why Cinnamon specifically? On Debian, Cinnamon is in the repos and has broad dependency hooks. Other DEs (GNOME, KDE) could pull in with the right combination of packages too — but Cinnamon is the most common surprise. The lean defaults (Fix 1) protect against all of them; the pin is a belt-and-suspenders hard stop specifically for Cinnamon.
Section 03
A display manager is a graphical login screen — GDM, LightDM, SDDM. It's a daemon that runs all the time waiting for you to log in. On a single-user machine, that's overhead for no reason. The alternative is two seconds of terminal.
You boot. You get a TTY — a text login prompt. You type your username and password.
You type startx. X launches, reads ~/.xinitrc,
and Fluxbox starts. You're in a graphical session in about three seconds.
When you log out of Fluxbox, you're back at the TTY. No service running in the background waiting for you. This is actually more responsive than a display manager on the same hardware.
If you want a graphical login screen — maybe on a machine other people use —
sudo apt install lightdm and it picks up Fluxbox automatically.
It's a completely reversible decision.
This file is what startx executes when X starts. It should do one thing.
exec replaces the shell process with Fluxbox rather than running it
as a child. When Fluxbox exits, the X session ends cleanly.
Don't put anything after the exec line — it won't run.
Startup tasks go in ~/.fluxbox/startup instead (Section 04).
If you want startx to run automatically when you log in on TTY1,
add this to the bottom of ~/.bash_profile:
This only fires on TTY1, not on other virtual terminals. You still get a full text session if you log in on TTY2 (Ctrl+Alt+F2).
Section 04
All Fluxbox config lives in ~/.fluxbox/. Three files do most of the work:
startup launches tray apps and services before Fluxbox takes over,
keys defines keyboard shortcuts,
and menu defines the right-click root menu.
~/.fluxbox/startup runs once when Fluxbox launches.
Launch tray applets here, then exec fluxbox at the end.
The & after each applet runs it in the background so the
script continues. exec fluxbox at the end has no & —
it replaces the startup script process with Fluxbox itself.
When Fluxbox exits, the session ends.
~/.fluxbox/keys maps key combinations to Fluxbox actions or shell commands.
These are the bindings I use daily.
amixer talks directly to ALSA. If you're running PipeWire
(which you should be — see Section 05), amixer bypasses
PipeWire's mixer and adjusts the ALSA hardware volume instead.
This creates a disconnect: PipeWire shows one volume, ALSA has another.
wpctl is PipeWire's own control utility. It adjusts the
PipeWire default sink directly, which is what pavucontrol
and pasystray also see. One source of truth.
If your media keys don't work after a fresh install,
log out and back in — PipeWire needs at least one session to initialize.
Right-click anywhere on the desktop opens ~/.fluxbox/menu.
This is your app launcher. No dock, no application grid — just a menu you define.
A minimal starting point:
After editing the menu file, right-click → System → Reconfigure to reload it without restarting Fluxbox. Changes take effect immediately.
Section 05
The old debate was ALSA vs PulseAudio. PipeWire ends it. It's the modern Linux audio stack, handles all the same use cases, and has better Bluetooth support than either. The reason I switched from ALSA: Bluetooth headphones that actually work without a fight.
Verify PipeWire is running after login:
ALSA alone works. It's in the kernel, always present, nothing to install. If you're on a desktop with wired audio, no Bluetooth, and no need for per-application volume control — ALSA is sufficient.
The friction shows up when you want Bluetooth audio, video conferencing apps that expect PulseAudio, or any app that needs to play audio alongside another app. ALSA handles one audio stream at a time without a mixer daemon.
If you go ALSA, replace the wpctl keybindings with:
Section 06
The Fluxbox slit is where single-purpose applets live. Each one does exactly one thing. The tray stack I run on a laptop:
wpctl — they talk to the same PipeWire session.
blueman
package which includes both the applet and a standalone manager.
Skip on a desktop without Bluetooth hardware.
Section 07
Before calling the setup done, run through this checklist. Each item confirms a different part of the stack is working correctly.
dpkg -l | grep cinnamon should return nothing.
If it does, you either installed something before setting up guardrails,
or the pin isn't in place yet.
apt-cache policy cinnamon should show
Candidate: (none) or Pin priority -1.
cat /etc/apt/apt.conf.d/99lean-desktop should return both
Install-Recommends "false" and Install-Suggests "false".
wpctl status shows Audio section with at least one sink.
If it hangs or errors, log out and back in — PipeWire initializes per-session.
wpctl get-volume @DEFAULT_AUDIO_SINK@
and confirm the value changed. If not, check the ~/.fluxbox/keys
binding and confirm wpctl is installed.
nm-applet should appear in the slit automatically.
If Wi-Fi isn't showing up, check that your interface isn't statically
declared in /etc/network/interfaces — NetworkManager won't
manage interfaces that are listed there.
startx and Fluxbox comes back up within
a few seconds. No errors in the terminal output.
The setup script — Appendix E
The full setup described on this page is automated in config-deb.sh.
It supports profiles (workstation, minimal, full, guardrails-only),
dry-run mode to preview what it would install, an interactive wizard,
and per-component flags (--no-printing, --alsa, etc.).
This page covers the decisions; the script automates the execution.
Why these choices, specifically
Every decision on this page has a reason that goes beyond "more minimal is better." See the companion case study for what each choice actually traces back to.