Devuan Server Installation Guide
Target: PC / Workstation (AMD Ryzen processor and NVIDIA GPU)
Filesystem: ext4
Desktop: None. Terminal / SSH
Init: sysvinit
Drive: NVMe SSD (/dev/nvme0n1)
The Devuan Wiki requires sign-up, however, official installation instructions are available at: devuan.org/os/documentation/install-guides/excalibur/install-devuan
Installation is straightforward using the netinstall option.
Before you begin:
- Ensure BIOS is set to UEFI (not legacy) - Enable virtualization (SVM/VT-x) - Disable secure boot (for NVIDIA drivers)
1. Download Devuan and Create a Bootable ISO
Download Devuan Excalibur from files.devuan.org/
- Choose devuan_excalibur (latest stable release)
- Choose installer_iso
- Download the _netinstall ISO
Create a bootable ISO with USB. PulsarTECH has a great Ventoy + Linux multi-boot USB guide available at: youtube.com/watch?v=BfjLJ0CqWsY
On Linux:
sudo dd if=devuan_excalibur_6.1.0_amd64_netinstall.iso of=/dev/sdX bs=4M status=progress conv=fsync
2. Boot the Live ISO
Boot the PC from USB. Here's a reference list of Boot Menu keys for various manufacturers:
| Manufacturer | Boot Menu Key | BIOS/UEFI Key |
| Acer | F12, Esc, F9 | F2, Delete |
| Asus | F8, Esc | F2, Delete |
| Dell | F12 | F2 |
| HP | F9, Esc | F10 |
| Lenovo | F12, F10, F8 (or Novo Button) | F2, F1 |
| MSI | F11 | Delete |
| Toshiba | F12 | F2, F1, Esc |
| Samsung | F12, F2, Esc | F2 |
| Sony VAIO | F11, F10, Esc (or Assist Button) | F2, F1, F3 |
| Gigabyte | F12 | Delete, F2 |
| Intel NUC | F10 | F2 |
3. Install Devuan
Once at the Devuan GUI screen, choose the Install option:
- Language, Location, Keyboard
- Select your system language, location, and keyboard layout.
- Network Configuration
- The installer will automatically configure the network if connected via ethernet. If using WiFi, provide the SSID and passphrase for your network.
- Enter a Hostname. Example:
devuanserver(any name is fine, as long as there are no spaces or special characters) - Leave the Domain name blank (unless you want to add)
- Root Password and User Account
- Set a Root password
- Add a user account. Example:
firstname - Set a password for the user
- Clock and Timezone
- Enter your time zone. Example: Eastern
- Disk Partitioning
- If setting up encryption (optional), follow these instructions for partitioning disks: devuan.org/os/documentation/install-guides/excalibur/full-disk-encryption.html
- If not setting up encryption (recommended for a simplified install), you can select
Guided - use entire diskorManual - If you select
Manual, Partition Layouts are provided below based on preference (Simple Partition Layout recommended) - After creating the partitions, write the changes to disks - select
<Yes>
- Package Manager Configuration
- Select a Devuan archive mirror. Preferred choice is
deb.devuan.org - If you need to use a HTTP proxy, you can enter the proxy info. Otherwise, just leave blank and select
<Continue>
- Select a Devuan archive mirror. Preferred choice is
- Popularity-Contest Configuration
- Participating in the package usage survey is optional. Select
<Yes>or<No>based on preference (<No>recommended)
- Participating in the package usage survey is optional. Select
- Software Selection
- Use the spacebar key to deselect everything then select only
SSH serverandstandard system utilities. Do not select any desktop environment. This produces a minimal headless server install
- Use the spacebar key to deselect everything then select only
- Init System Selection
- Options for init are:
sysvinit(default, classic, well-documented)OpenRC(modern, dependency-based, popular in Gentoo)runit(minimalist, fast boot)
- For default init (most stable and compatible), select
sysvinit - If you prefer a more modern and dependency-based init system (that's closer to systemd in usage), choose
openrc
- Options for init are:
- Boot Loader
- Install GRUB to the EFI partition. When asked to install GRUB boot loader to primary drive, select
<Yes> - Select
/dev/nvme0n1as the device for the boot loader installation
- Install GRUB to the EFI partition. When asked to install GRUB boot loader to primary drive, select
- Finish and Reboot
- The installation is now complete. Select <Continue> to reboot
- Remove the USB drive immediately after rebooting
Simple Partition Layout (Recommended)
| # | Mount | Size | Filesystem | Purpose |
|---|---|---|---|---|
| 1 | /boot/efi | 1GB | FAT32 | Required for UEFI booting |
| 2 | /boot | 2GB | ext4 | OS partition to simplify recovery |
| 3 | / | Remaining | ext4 | Data storage |
Design Rationale
- Keep the server simple
- Reduce fragmentation and issues with partition sizes
- Optimize for running Docker and containerized apps
- Using NAS for bulk storage to offload media
If you prefer an enterprise-level and isolated partition style, you can opt for:
Enterprise Partition Layout
| # | Mount Point | Size | Filesystem | Purpose |
|---|---|---|---|---|
| 1 | /boot/efi |
512 MiB | FAT32 (EFI System Partition) | UEFI bootloader |
| 2 | /boot |
2 GiB | ext4 | Kernel and initramfs images |
| 3 | / |
50 GiB | ext4 | Root filesystem |
| 4 | /var |
100 GiB | ext4 | Logs, databases, container layers, package cache |
| 5 | /tmp |
10 GiB | ext4 (mounted noexec,nosuid,nodev) | Temporary files |
| 6 | swap |
32 GiB | Linux swap | Swap space (roughly 1/3 of max RAM) |
| 7 | /home |
50 GiB | ext4 | User home directories |
| 8 | /srv |
Remainder | ext4 or XFS | Server data, VMs, containers, datasets |
Design Rationale
- Separate
/var: Runaway logs or container image accumulation cannot fill the root filesystem and crash the system. - Separate
/tmp: Mounted withnoexec,nosuid,nodevfor hardening; prevents it from impacting other partitions. - 32 GiB swap: With up to 96 GB RAM and potential GPU/CUDA workloads, 32 GiB provides comfortable headroom for memory pressure without being wasteful. If you run memory-intensive AI/ML inference, consider increasing this.
- Large
/srv: The bulk of a headless server's storage goes here — VM disk images, container volumes, datasets, NFS exports, and similar. - Separate
/boot: Ensures the bootloader and kernel images are always accessible regardless of root filesystem issues.
4. Post-Install: First Boot Setup
Log in as root at the terminal (using password set from prior steps).
- Verify network connectivity:
ip addr show
ping -c 3 devuan.org
- Update the system:
apt update && apt upgrade -y
- Configure APT sources:
nano /etc/apt/sources.list
# Ensure contrib, non-free, and non-free-firmware are enabled
deb http://deb.devuan.org/merged excalibur main contrib non-free non-free-firmware
deb http://deb.devuan.org/merged excalibur-updates main contrib non-free non-free-firmware
deb http://deb.devuan.org/merged excalibur-security main contrib non-free non-free-firmware
- Update again:
apt update
- Install essential server packages:
apt install -y \
sudo vim neovim htop tmux curl wget git \
lm-sensors smartmontools nvme-cli \
unattended-upgrades apt-listchanges \
ufw \
build-essential dkms linux-headers-amd64
- Add user to sudo:
usermod -aG sudo yourusername
- Create a Swap File (if you created the Simple Partition Layout)
Swap provides a safety buffer when system memory is exhausted. While modern systems with high RAM can run without swap, it is still recommended for stability especially when running Docker containers, databases, or GPU workloads.
Instead of creating a dedicated swap partition with the Simple Partition layout, using a swap file is preferred. It is easier to manage, resize, and remove without repartitioning disks.
The example below creates a 16 GB swap file. Adjust the size based on your system:
fallocate -l 16G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
Add the following line to /etc/fstab so the swap file is enabled on boot:
echo '/swapfile none swap sw 0 0' >> /etc/fstab
Verify Swap
swapon --show
free -h
Sizing Guidance
- 16 GB RAM or less: 8–16 GB swap recommended
- 32–64 GB RAM: 8–16 GB swap sufficient
- 64 GB+ RAM: 4–16 GB swap for safety buffer
- Heavy GPU / AI workloads: Consider 16–32 GB swap
5. Install and Configure Tailscale
Tailscale provides secure, zero-config WireGuard-based mesh networking. All SSH access will go through the Tailscale network, meaning no SSH port is exposed to the public internet.
- Devuan Excalibur is based on Debian Trixie, so use the Trixie repository:
mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkgs.tailscale.com/stable/debian/trixie.noarmor.gpg \
| tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/debian/trixie.tailscale-keyring.list \
| tee /etc/apt/sources.list.d/tailscale.list
apt update
- Install Tailscale:
apt install -y tailscale
Important: Tailscale's official packages ship with a systemd service file but no sysvinit script. Since Devuan uses sysvinit, you need to create one manually after installing.
- The package only includes a systemd unit, so you must create an init script manually:
cat > /etc/init.d/tailscaled << 'INITEOF'
#!/bin/sh
### BEGIN INIT INFO
# Provides: tailscale
# Required-Start: $local_fs $network $all
# Required-Stop: $local_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Tailscale daemon
# Description: Runs the tailscaled mesh VPN daemon.
### END INIT INFO
. /lib/lsb/init-functions
PIDFILE=/var/run/tailscale.pid
LOGFILE=/var/log/tailscale.log
TAILSCALED=/usr/sbin/tailscaled
fail_unless_root() {
if [ "$(id -u)" != '0' ]; then
log_failure_msg "must be run as root"
exit 1
fi
}
case "$1" in
start)
fail_unless_root
log_daemon_msg "Starting Tailscale daemon" "tailscaled"
$TAILSCALED --cleanup
start-stop-daemon --start --background --no-close \
--exec $TAILSCALED \
--pidfile "$PIDFILE" \
--make-pidfile \
-- \
--state=/var/lib/tailscale/tailscaled.state \
--socket=/run/tailscale/tailscaled.sock >> $LOGFILE 2>&1
status=$?
log_end_msg $status
;;
stop)
fail_unless_root
log_daemon_msg "Stopping Tailscale daemon" "tailscaled"
start-stop-daemon --stop --pidfile "$PIDFILE" \
--remove-pidfile --retry 10
status=$?
log_end_msg $status
;;
restart)
$0 stop
sleep 1
$0 start
;;
status)
status_of_proc -p "$PIDFILE" "$TAILSCALED" "tailscaled"
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
INITEOF
chmod +x /etc/init.d/tailscaled
- Enable and start Tailscale:
update-rc.d tailscaled defaults
mkdir -p /run/tailscale
service tailscaled start
service tailscaled status
- Authenticate to your Tailnet:
tailscale up
This will print a URL. Open it in a browser on another device, sign in to your Tailscale account, and authorize the machine. Once authenticated, verify connectivity:
tailscale status
tailscale ip -4
Note the Tailscale IP (typically 100.x.y.z). This is the address you will use for SSH.
- Enable Tailscale SSH:
Tailscale SSH lets you authenticate SSH sessions through your Tailscale identity provider, eliminating the need for SSH keys or passwords entirely:
tailscale set --ssh
When using Tailscale SSH, connections are authenticated by your Tailscale ACLs instead of local SSH keys. You can configure who has access in the Tailscale admin console under Access Controls.
Note: If you enabled Tailscale SSH in step 5.6.6, you can optionally disable the local SSH daemon entirely since Tailscale handles SSH directly:
service ssh stop
update-rc.d ssh disable
- Test SSH over Tailscale from your other machine before disconnecting the monitor:
You can also use the name of your machine on your Tailnet instead of the Tailscale IP, if you have Tailscale MagicDNS enabled.
- Configure the Firewall:
Since SSH is only accessible via Tailscale, the firewall should block SSH from the public interfaces entirely. Only open ports for services you explicitly need on the LAN.
ufw default deny incoming
ufw default allow outgoing
# Allow all traffic on the Tailscale interface (trusted mesh network)
ufw allow in on tailscale0
# Do NOT allow SSH on public interfaces — it is Tailscale-only
# ufw allow ssh <-- intentionally omitted
ufw enable
If you later run services that need LAN access (e.g., NFS, a web server, Samba), add rules for those specific ports on the specific LAN interfaces:
# Example: allow HTTP on the 2.5G LAN interface only
ufw allow in on enp2s0 to any port 80 proto tcp
6. Install NVIDIA GPU Drivers
The open-source Nouveau driver must be disabled before installing the proprietary NVIDIA driver:
cat > /etc/modprobe.d/blacklist-nouveau.conf << 'EOF'
blacklist nouveau
options nouveau modeset=0
EOF
update-initramfs -u
reboot
After rebooting, log back in and install:
sudo apt update
sudo apt install linux-headers-$(uname -r) build-essential libglvnd-dev pkg-config dkms
Detect and install driver:
sudo nvidia-detect
sudo apt install nvidia-driver nvidia-kernel-dkms nvidia-smi nvidia-settings
Only use backports if the default driver fails for your GPU:
sudo apt install nvidia-driver firmware-misc-nonfree
Verify DKMS build:
dkms status
You should see a line like:
nvidia/550.163.01, 6.12.x-amd64, x86_64: installed
Required reboot and verify:
reboot
After reboot, confirm the driver is loaded:
nvidia-smi
Expected output will show the GPU with driver version, CUDA version, temperature, and memory usage. For a headless server with no display attached, nvidia-smi is the primary way to verify the GPU is operational.
- Enable Persistence Mode (Headless):
Without an X server running, the NVIDIA driver may unload between GPU tasks, adding latency. Enable persistence mode:
nvidia-smi -pm 1
To make this persistent across reboots, create an init script. For sysvinit:
cat > /etc/init.d/nvidia-persistenced << 'INITEOF'
#!/bin/sh
### BEGIN INIT INFO
# Provides: nvidia-persistenced
# Required-Start: $local_fs
# Required-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: NVIDIA Persistence Daemon
### END INIT INFO
case "$1" in
start)
/usr/bin/nvidia-smi -pm 1
;;
stop)
/usr/bin/nvidia-smi -pm 0
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
;;
esac
exit 0
INITEOF
chmod +x /etc/init.d/nvidia-persistenced
update-rc.d nvidia-persistenced defaults
- Install CUDA Toolkit:
If you need CUDA for compute workloads (AI inference, GPU-accelerated applications):
apt install -y nvidia-cuda-toolkit
Verify:
nvcc --version
7. Install Docker
Docker CE's official Debian packages include a sysvinit init script (/etc/init.d/docker), so Docker runs natively on Devuan without systemd.
- Remove Conflicting Packages
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do
apt-get remove -y $pkg 2>/dev/null
done
- Add the Docker Repository
Since Devuan Excalibur is based on Debian Trixie, use the Trixie repository:
apt install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
cat > /etc/apt/sources.list.d/docker.list << EOF
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian trixie stable
EOF
apt update
- Install Docker Engine
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- Enable and Start Docker (sysvinit)
The Docker package ships with /etc/init.d/docker. Enable it at boot and start it:
update-rc.d docker defaults
service docker start
Verify Docker is running:
service docker status
docker info
- Allow Your User to Run Docker
Add your regular user to the docker group so you don't need sudo for every Docker command:
usermod -aG docker yourusername
Log out and back in (or newgrp docker) for the group change to take effect.
Security note: Membership in the docker group grants root-equivalent access to the host. Only add trusted users.
- Test the Installation
docker run --rm hello-world
- NVIDIA Container Toolkit (GPU Containers)
To use the NVIDIA GPU inside Docker containers (for CUDA, AI inference, etc.), install the NVIDIA Container Toolkit:
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
| gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -fsSL https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
| tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
apt update
apt install -y nvidia-container-toolkit
Configure the Docker runtime:
nvidia-ctk runtime configure --runtime=docker
service docker restart
Test GPU access from a container:
docker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi
You should see the NVIDIA GPU listed with driver version and CUDA version.
Docker Compose Quick Reference
Docker Compose is installed as a CLI plugin. Use it with:
docker compose up -d # Start services in background
docker compose down # Stop and remove services
docker compose logs -f # Follow logs
docker compose ps # List running services
8. NFS NAS Mounts
If you are mounting external storage NAS drives (e.g., Synology, QNAP, TrueNAS), follow instructions below.
- Install NFS Client Packages
apt install -y nfs-common
- Create Mount Points
Create directories for each NFS share you want to mount. A clean convention is to mount them under /mnt/nas/:
mkdir -p /mnt/nas/media
mkdir -p /mnt/nas/backups
mkdir -p /mnt/nas/shared
Adjust the names to match your NAS export structure.
- Test Mounts Manually
Before making them permanent, verify each mount works:
mount -t nfs4 nas.local:/volume1/media /mnt/nas/media
ls /mnt/nas/media
Replace nas.local with the IP or hostname of your NAS, and /volume1/media with the actual NFS export path. If using NFSv3:
mount -t nfs -o vers=3 nas.local:/volume1/media /mnt/nas/media
- Configure Permanent Mounts in /etc/fstab
Once the manual test succeeds, add entries to /etc/fstab for automatic mounting at boot:
nano /etc/fstab
# NAS NFS Mounts
nas.local:/volume1/media /mnt/nas/media nfs4 rw,soft,intr,timeo=30,retrans=3,_netdev 0 0
nas.local:/volume1/backups /mnt/nas/backups nfs4 rw,soft,intr,timeo=30,retrans=3,_netdev 0 0
nas.local:/volume1/shared /mnt/nas/shared nfs4 rw,soft,intr,timeo=30,retrans=3,_netdev 0 0
Mount options explained:
rw: Read-write access. Userofor read-only shares like media libraries.soft: Returns an error if the NAS is unreachable instead of hanging indefinitely. Critical for a headless server — ahardmount will cause processes to freeze and become unkillable if the NAS goes offline.intr: Allows NFS operations to be interrupted by signals (e.g., Ctrl+C).timeo=30: Timeout in deciseconds (3 seconds) before retrying.retrans=3: Number of retries before reporting failure._netdev: Tells the init system this mount requires the network to be up first, preventing boot hangs if the NAS is unreachable.
Mount all new fstab entries:
mount -a
Verify:
df -h | grep nas
- NFS Mount Permissions
NFS permissions depend on how your NAS exports are configured. Common approaches:
UID/GID mapping (recommended): Ensure the UID and GID of your Devuan user match the NFS export's expected UID/GID. Check with id yourusername on Devuan and compare against the NAS settings.
all_squash with anonuid/anongid: If the NAS export uses all_squash, all access is mapped to a single UID/GID defined on the NAS side. This is the simplest for shared access.
No root squash: Only enable no_root_squash on the NAS if you specifically need root write access from this server. This is a security risk and is generally unnecessary.
- Using NFS Mounts in Docker Containers
Bind-mount the host NFS path
In your compose.yaml:
yaml
services:
myapp:
image: myapp:latest
volumes:
- /mnt/nas/media:/data/media
- /mnt/nas/shared:/data/shared
This is the simplest approach. The host handles the NFS connection and containers see the data as a regular directory.
- Monitoring NFS Mount Health
NFS mounts can silently become stale if the NAS reboots or the network hiccups. Create a simple health check cron job:
cat > /etc/cron.d/nfs-health << 'EOF'
*/5 * * * * root /usr/local/bin/nfs-health-check.sh
EOF
cat > /usr/local/bin/nfs-health-check.sh << 'SCRIPT'
#!/bin/sh
for mount in /mnt/nas/media /mnt/nas/backups /mnt/nas/shared; do
if mountpoint -q "$mount"; then
timeout 5 ls "$mount" > /dev/null 2>&1
if [ $? -ne 0 ]; then
logger -t nfs-health "STALE mount detected: $mount — attempting remount"
umount -l "$mount" 2>/dev/null
mount "$mount"
fi
else
logger -t nfs-health "Mount missing: $mount — attempting mount"
mount "$mount"
fi
done
SCRIPT
chmod +x /usr/local/bin/nfs-health-check.sh
This checks every 5 minutes that each NFS mount is responsive, and attempts a remount if a share has gone stale. Check the results in /var/log/syslog with grep nfs-health /var/log/syslog.
9. Fastfetch
"Obligatory neofetch" (fastfetch):
sudo apt update
sudo apt install fastfetch
Result
- No systemd
- Full Docker support
- GPU acceleration working
- NAS mounts