Skip to content

Debian / Ubuntu Setup

This page covers machine-level setup for Debian-based distributions — primarily Ubuntu (the most common developer-facing Debian derivative) with notes for vanilla Debian where behavior differs. It is the prerequisite to the Onboarding Runbook.

Ubuntu's defaults are more developer-friendly out of the box than Fedora's (codecs are offered during install, drivers are detected automatically), but its snap strategy creates friction that this page addresses directly.

Post-install checklist (first boot)

# 1. System update
sudo apt update && sudo apt upgrade -y

# 2. Install basic tools immediately (needed for everything else)
sudo apt install -y curl wget git software-properties-common

# 3. Firmware updates (Ubuntu)
sudo fwupdmgr get-devices
sudo fwupdmgr refresh --force
sudo fwupdmgr get-updates
sudo fwupdmgr update

# 4. Reboot (kernel updates, firmware)
sudo reboot

apt configuration

apt's defaults are reasonable. Two additions improve the developer experience:

Parallel downloads

Create /etc/apt/apt.conf.d/99parallel:

Acquire::Queue-Mode "host";
Acquire::http::Pipeline-Depth "10";

Automatic security updates

Ubuntu enables unattended-upgrades for security by default. Verify it is active:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

On vanilla Debian, this is not enabled by default — install and configure it explicitly.

Backports (Debian only)

Vanilla Debian's stable repositories ship older tool versions. Enable backports for access to newer packages without switching to testing:

# /etc/apt/sources.list.d/backports.list
echo "deb http://deb.debian.org/debian $(lsb_release -cs)-backports main" | \
  sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update

Install specific packages from backports with:

sudo apt install -t $(lsb_release -cs)-backports <package>

The snap problem

Ubuntu ships Firefox, Thunderbird, and increasingly other GUI applications as snaps. For development tools, snaps introduce three problems:

  1. Startup latency — snap applications decompress on launch. First launch of a snap app after boot takes 3-10 seconds.
  2. Sandbox restrictions — snaps cannot access arbitrary host paths without manual interface connections. This breaks tools that need to see ~/.config, /tmp, or project directories outside $HOME.
  3. PATH and socket isolation — snap-installed tools cannot see mise shims, the Podman socket, or SSH agent sockets without per-snap configuration.

The framework's position: remove snap versions of developer tools and install native .deb packages or upstream binaries.

Replacing snap Firefox (Ubuntu)

# Remove snap Firefox
sudo snap remove firefox

# Install Mozilla's official PPA
sudo add-apt-repository ppa:mozillateam/ppa

# Pin PPA version over snap
echo '
Package: *
Pin: release o=LP-PPA-mozillateam
Pin-Priority: 1001
' | sudo tee /etc/apt/preferences.d/mozilla-firefox

# Prevent apt from re-installing the snap transitional package
echo 'Package: firefox*
Pin: release o=Ubuntu
Pin-Priority: -1
' | sudo tee -a /etc/apt/preferences.d/mozilla-firefox

sudo apt update
sudo apt install firefox

Preventing snap reinstallation

Ubuntu's ubuntu-desktop metapackage can pull snap packages back in during upgrades. Create a preference that blocks the snap transitional packages:

# Prevent snap transitional packages from being installed
sudo apt-mark hold firefox snap

Pragmatism over purity

This framework does not advocate removing snapd entirely. Snap is fine for consumer applications (Slack, Spotify, Discord) where sandbox isolation is a benefit. The guidance is specific: development tools that need host integration should not be snaps.

Multimedia codecs

Ubuntu offers restricted extras during installation. If skipped:

# Ubuntu
sudo apt install ubuntu-restricted-extras

# Debian
sudo apt install libavcodec-extra

For hardware-accelerated video:

# Intel
sudo apt install intel-media-va-driver-non-free

# AMD
sudo apt install mesa-va-drivers

# NVIDIA (if using proprietary driver)
sudo apt install nvidia-vaapi-driver

Filesystem and storage

Enable fstrim for SSDs

sudo systemctl enable --now fstrim.timer

ext4 considerations

Ubuntu defaults to ext4 (not Btrfs like Fedora). ext4 is stable and fast with no special developer-facing tuning needed. If using Btrfs (Debian installer offers it, Ubuntu does not by default), see the Fedora page's Btrfs section — the same chattr +C guidance for container storage applies.

Firewall

Ubuntu ships with ufw (Uncomplicated Firewall) — a frontend to iptables/nftables. It is installed but not enabled by default on desktop Ubuntu. Enable it:

sudo ufw enable
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Verify
sudo ufw status verbose

Common developer additions:

# Allow a dev server from LAN
sudo ufw allow 3000/tcp

# Allow SSH (if doing remote development on this machine)
sudo ufw allow ssh

# Remove when done
sudo ufw delete allow 3000/tcp

# Check rules
sudo ufw status numbered

On vanilla Debian, ufw is available but not pre-installed:

sudo apt install ufw

AppArmor

Ubuntu uses AppArmor (not SELinux) as its mandatory access control framework. Like the Fedora page's SELinux guidance: leave it enabled. Do not disable AppArmor to fix a development problem.

# Check status
sudo aa-status

# See recent denials
sudo dmesg | grep -i apparmor

# Set a profile to complain mode (logging without enforcement)
sudo aa-complain /usr/bin/some-program

# Re-enable enforcement
sudo aa-enforce /usr/bin/some-program

Container interaction: Podman on Ubuntu works with AppArmor's default policies. Unlike SELinux's :z/:Z suffix requirement on Fedora, bind mounts in Podman on Ubuntu/Debian work without special labeling in most cases.

Systemd services for development

SSH agent as a user service

Identical to the Fedora setup — see the Onboarding Runbook for the full systemd unit.

systemctl --user enable --now ssh-agent

Podman socket

systemctl --user enable --now podman.socket
export DOCKER_HOST="unix://${XDG_RUNTIME_DIR}/podman/podman.sock"

Journal size management

sudo journalctl --vacuum-size=500M

Edit /etc/systemd/journald.conf:

[Journal]
SystemMaxUse=500M

GNOME desktop tuning

Ubuntu ships GNOME on Wayland (with modifications). The framework's assumption of GNOME, why, and what changes if you run KDE/Sway instead are covered in the Fedora page's GNOME note; it applies identically here. The same tuning as Fedora applies:

# Key repeat speed
gsettings set org.gnome.desktop.peripherals.keyboard delay 200
gsettings set org.gnome.desktop.peripherals.keyboard repeat-interval 25

# Caps Lock as Ctrl
gsettings set org.gnome.desktop.input-sources xkb-options "['caps:ctrl_modifier']"

# Disable hot corner
gsettings set org.gnome.desktop.interface enable-hot-corners false

# Font rendering
gsettings set org.gnome.desktop.interface font-antialiasing 'rgba'
gsettings set org.gnome.desktop.interface font-hinting 'slight'

Ubuntu-specific: minimize button

Ubuntu's GNOME adds a minimize button by default. If using a tiling workflow or keyboard-driven window management, remove it:

gsettings set org.gnome.desktop.wm.preferences button-layout 'close:'

Power management (laptops)

# TLP for advanced power management
sudo apt install tlp tlp-rdw
sudo systemctl enable --now tlp

# Ubuntu uses power-profiles-daemon by default; TLP replaces it
sudo systemctl mask power-profiles-daemon

For ThinkPads:

sudo apt install tp-smapi-dkms acpi-call-dkms

Developer prerequisites

Install before running bootstrap.sh:

sudo apt install \
  zsh git curl wget \
  build-essential cmake pkg-config \
  libssl-dev zlib1g-dev libreadline-dev \
  libffi-dev libyaml-dev \
  libsqlite3-dev libpq-dev \
  jq \
  libsecret-tools \
  podman buildah skopeo \
  shellcheck

fd and bat naming quirk — sidestepped entirely

Debian/Ubuntu ship fd as fdfind and bat as batcat due to package-name conflicts. This framework never hits that: fd and bat are mise-managed (aqua-backed GitHub release binaries), so they arrive under their real names. If you ever install the apt versions instead, you will need fd=fdfind / bat=batcat aliases yourself — the framework does not ship them.

Why each group:

  • zsh, git, curl, wget — framework hard dependencies.
  • build-essential, cmake, pkg-config, lib*-dev — build toolchain for native extensions. Without these, compiling Ruby, Python C extensions, and Node native addons fails.
  • jq — pre-mise CLI dependency (also mise-managed later; see the overlap note below). bat, fd, ripgrep, fzf, and direnv are deliberately NOT in this list — they are declared in mise's [tools] with aqua-backed resolution and arrive with mise bootstrap; their shell wiring is command -v guarded.
  • libsecret-tools — provides secret-tool, used by the keychain_get shell function.
  • podman, buildah, skopeo — container toolchain. Note: on older Ubuntu (22.04), Podman may need the Kubic repository for a current version.

Deliberate overlap with mise [tools], and two gaps

jq, shellcheck, zsh, and libsecret-tools are also declared in the mise config and will be managed by mise after mise bootstrap — mise's shims sit earlier on PATH, so the mise-managed versions win from then on. The apt copies cover the window before mise exists. Two tools deserve the same treatment but have unreliable apt package names across releases, so they are not in the list above:

  • gh — worth installing before mise bootstrap if your release ships it (sudo apt install gh, or GitHub's own apt repo): gh auth login raises the GitHub API limit from 60 to 5000 requests/hour, and mise's github: backend and attestation checks spend that budget during the bootstrap itself.
  • git-delta — git's configured pager. Until mise delivers delta, paged git commands fail; either install it if your release has it, or ride out the few minutes between bootstrap.sh and mise bootstrap with git -c core.pager=less <cmd>.
  • shellcheck — shell script static analysis for pre-commit hooks.

Podman version (Ubuntu 22.04)

Ubuntu 22.04's default Podman package is old (3.4.x). Historically the fix was the Kubic (devel:kubic:libcontainers) OBS repository — but that project has been discontinued and its repos are gone, so the old kubic instructions no longer work. On 22.04 today, either:

  • upgrade to Ubuntu 24.04+ (ships Podman 4.9.x — current enough for rootless networking and podman compose with a provider), or
  • use a maintained alternative (the podman PPA / static builds), or build from source.
# Ubuntu 24.04+ : Podman 4.9.x is in the default repos, no extra setup.
sudo apt install podman
podman --version

Do not copy the old download.opensuse.org/.../kubic/... source lines — they 404.

VS Code installation

Do not use the snap version of VS Code. Install from Microsoft's apt repository:

Microsoft now recommends the deb822 .sources format (easier to audit and remove than the legacy one-line .list), with the key under /usr/share/keyrings:

wget -qO- https://packages.microsoft.com/keys/microsoft.asc | \
  gpg --dearmor | sudo tee /usr/share/keyrings/microsoft.gpg > /dev/null

sudo tee /etc/apt/sources.list.d/vscode.sources > /dev/null <<'EOF'
Types: deb
URIs: https://packages.microsoft.com/repos/code
Suites: stable
Components: main
Architectures: amd64,arm64,armhf
Signed-By: /usr/share/keyrings/microsoft.gpg
EOF

sudo apt update
sudo apt install code

After this page

Proceed to the Onboarding Runbook for framework installation (bootstrap.sh, mise, SSH keys, git identity).