Appendix B

Debian + Fluxbox
A Real-World Setup

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.

Appendix B
Opinionated guide
Debian Testing + Fluxbox

Section 01

The stack, and why each piece is there.

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.

Core · Required
Debian Testing
sources.list: testing main non-free-firmware
Rolling release means current Mesa, current kernel, current everything. No reinstalls. No version-upgrade friction. Testing has never broken my daily driver in a way I couldn't fix in under an hour. See Appendix A for the install.
Core · Required
Xorg + xinit
xorg xinit
X11. Fluxbox is X-native — no Wayland support. xinit gives you startx, which is all you need to launch a graphical session from the terminal. No display manager required.
Core · Required
Fluxbox
fluxbox
Floating WM with a built-in toolbar, slit, and window tabbing. Configured entirely with text files. Right-click on the desktop opens a menu you define. Fast. Stays out of the way.
Core · Required
NetworkManager
network-manager network-manager-gnome
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.
Core · Recommended
Thunar
thunar thunar-volman
XFCE's file manager, but it doesn't require XFCE. GTK, fast, handles gvfs mounts, and thunar-volman gives you USB automounting. PCManFM is the lighter alternative if you want to go further down the rabbit hole.
Core · Recommended
Fonts + display tools
fonts-dejavu arandr numlockx xbacklight
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.
Optional · Tray
Battery + Bluetooth
cbatticon blueman
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.
Optional · Audio
PipeWire stack
pipewire-audio wireplumber pavucontrol pasystray
PipeWire handles ALSA, PulseAudio, and JACK compatibility in one daemon. Bluetooth audio works out of the box. pavucontrol gives you per-app volume. pasystray sits in the slit. Media keys use wpctl. See Section 05.
Optional · Daily use
Browser
firefox-esr or chromium
Both are in the Debian repo. Firefox ESR is the default recommendation — stable release track, ships with Debian officially. Chromium is the open-source build of Chrome without the Google telemetry. Pick one; you can have both.
Optional · Daily use
Utilities
evince vlc libreoffice xtrlock
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.
Not included · Why
Display manager
GDM / LightDM / SDDM — all skipped
A display manager is a login screen. On a single-user machine, it's overhead with no benefit. startx from a TTY is faster, uses no background resources, and requires no configuration. See Section 03.
Not included · Why
Notification daemon
dunst / mako — situational
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

APT guardrails. The first thing you configure.

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."

Fix 1 — Lean APT defaults

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.

/etc/apt/apt.conf.d/99lean-desktop
APT::Install-Recommends "false";
APT::Install-Suggests "false";

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.

Fix 2 — Cinnamon pin

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.

/etc/apt/preferences.d/no-cinnamon.pref
# Never install Cinnamon or its meta-packages

Package: cinnamon
Pin: release *
Pin-Priority: -1

Package: cinnamon-*
Pin: release *
Pin-Priority: -1

Package: task-cinnamon-desktop
Pin: release *
Pin-Priority: -1

Package: cinnamon-desktop-environment
Pin: release *
Pin-Priority: -1

You can verify the pin took effect:

apt-cache policy cinnamon
# Should show: Candidate: (none) or Pin: -1

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

Starting X. No display manager needed.

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.

~/.xinitrc

This file is what startx executes when X starts. It should do one thing.

~/.xinitrc
#!/bin/sh
exec startfluxbox

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).

Auto-start X on login (optional)

If you want startx to run automatically when you log in on TTY1, add this to the bottom of ~/.bash_profile:

~/.bash_profile
# Auto-start X on login if on TTY1
if [[ -z $DISPLAY ]] && [[ $(tty) == /dev/tty1 ]]; then
  exec startx
fi

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

Fluxbox configuration.

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.

Startup script

~/.fluxbox/startup runs once when Fluxbox launches. Launch tray applets here, then exec fluxbox at the end.

~/.fluxbox/startup
#!/bin/sh

# Enable numlock
numlockx &

# Network tray applet
nm-applet &

# Bluetooth (remove if no BT or on desktop)
blueman-applet &

# Battery icon (remove if on desktop)
cbatticon &

# PipeWire volume tray icon
pasystray &

# Set desktop background (fbsetbg picks last used)
fbsetbg -l &

# Launch Fluxbox — must be last, without &
exec fluxbox

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.

Keybindings

~/.fluxbox/keys maps key combinations to Fluxbox actions or shell commands. These are the bindings I use daily.

~/.fluxbox/keys
# Window management
Mod1 Tab :NextWindow {workspace=[current]}
Mod1 Shift Tab :PrevWindow {workspace=[current]}
Mod1 F4 :Close
Mod1 z :RootMenu

# Launch terminal (Alt+X)
Mod1 x :ExecCommand xterm

# Brightness (requires xbacklight)
XF86MonBrightnessUp :ExecCommand xbacklight -inc 5
XF86MonBrightnessDown :ExecCommand xbacklight -dec 5

# PipeWire media keys (use wpctl, not amixer)
XF86AudioLowerVolume :ExecCommand wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
XF86AudioRaiseVolume :ExecCommand wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
XF86AudioMute :ExecCommand wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle

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 menu

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:

~/.fluxbox/menu
[begin] (Menu)
  [exec] (Terminal) {xterm}
  [exec] (Files) {thunar}
  [exec] (Browser) {firefox-esr}
  [exec] (Editor) {gedit}
  [exec] (Display) {arandr}
  [exec] (Audio) {pavucontrol}
  [exec] (Network) {nm-connection-editor}
  [separator]
  [submenu] (System)
    [exec] (Reconfigure) {fluxbox-remote reconfigure}
    [restart] (Restart Fluxbox)
    [exit] (Log out)
  [end]
[end]

After editing the menu file, right-click → System → Reconfigure to reload it without restarting Fluxbox. Changes take effect immediately.

Section 05

Audio. PipeWire is the right answer.

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.

# Install the PipeWire stack
sudo apt install pipewire-audio wireplumber pavucontrol pasystray
Core
pipewire-audio
Meta-package that pulls in the PipeWire daemon and compatibility layers for ALSA and PulseAudio apps. Existing software that uses either API continues to work.
Core
wireplumber
The session manager for PipeWire. Handles device routing — which app sends audio to which device. Without it, PipeWire runs but doesn't do much.
Optional · UI
pavucontrol
PulseAudio Volume Control — works with PipeWire too. Per-application volume sliders, input/output device selection. Launch from the Fluxbox right-click menu when you need it.
Optional · Tray
pasystray
Tray icon for PipeWire/PulseAudio. Right-click to adjust volume or switch output device. Lives in the Fluxbox slit. Scroll wheel on the icon adjusts volume.

Verify PipeWire is running after login:

wpctl status
# Should show Audio and Video sections with your devices listed

# Test volume control directly:
wpctl set-volume @DEFAULT_AUDIO_SINK@ 50%
wpctl get-volume @DEFAULT_AUDIO_SINK@

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:

# ALSA volume keys (in ~/.fluxbox/keys)
XF86AudioLowerVolume :ExecCommand amixer set Master 5%- unmute
XF86AudioRaiseVolume :ExecCommand amixer set Master 5%+ unmute
XF86AudioMute :ExecCommand amixer set Master toggle

Section 06

The tray stack. Small tools, single purpose.

The Fluxbox slit is where single-purpose applets live. Each one does exactly one thing. The tray stack I run on a laptop:

nm-applet
cbatticon
pasystray
blueman-applet
dunst (not running)
Network
nm-applet
NetworkManager's tray icon. Right-click to see available Wi-Fi networks, connect, disconnect, or open the full connection editor. Left-click shows current connection. This is the reason you install NetworkManager instead of managing connections manually.
Battery
cbatticon
Battery status icon. Shows charge percentage and charging state. Sends a notification when battery is low (if a notification daemon is running). Tiny binary, no dependencies beyond what's already installed. Skip on a desktop.
Audio
pasystray
PipeWire/PulseAudio volume tray icon. Scroll wheel adjusts default sink volume. Right-click opens output device selector. Left-click opens a minimal volume slider. Works alongside wpctl — they talk to the same PipeWire session.
Bluetooth
blueman-applet
Full Bluetooth management from the slit. Pair devices, connect and disconnect, manage trusted devices. Pulls in the blueman package which includes both the applet and a standalone manager. Skip on a desktop without Bluetooth hardware.

Section 07

Post-install verification.

Before calling the setup done, run through this checklist. Each item confirms a different part of the stack is working correctly.

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.

Appendix E: config-deb.sh — full script with line numbers →

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.

Case Study: The 2025 Refresh — Why These Choices →