Skip to main content

Arch Homelab - Self-Hosted Everything

Complete guide to building a self-hosted homelab with Arch Linux, MergerFS, SnapRAID, Docker, and automated media services

Arch Homelab - Self-Hosted Everything

Build Your Arch Linux Homelab

Overview

This guide covers installing Arch Linux (LTS) and configuring it as a powerful home server. From base installation to containerized services, you'll build a complete self-hosted infrastructure that rivals commercial cloud solutions.

Requirements

What we're building:

  • Unified Storage Pool: Combine drives of different sizes with a single mount point using MergerFS
  • Hot-Swappable Storage: Add new drives without downtime or complex reconfiguration
  • Data Protection: SnapRAID parity protection against drive failures
  • Automated Backups: Scheduled backups of data and configurations
  • Network Storage: NAS array accessible to all your devices
  • Media Automation: Automated downloading, sorting, and tagging for movies, TV, music, and anime
  • Personal Streaming: Self-hosted media server for family and friends
  • Private Cloud: Your own cloud server accessible from anywhere
  • Auto-Updates: Automatic service updates via Watchtower
  • Easy Deployment: Containerized apps without dependency conflicts

Install Arch Linux LTS

Download Latest Arch Image

Install transmission torrent client:

		sudo pacman -S transmission-cli --noconfirm
	

Start daemon:

		transmission-daemon
	

Set download directory:

		sudo mkdir ~/Downloads/Torrents
transmission-remote -w ~/Downloads/Torrents
	

Download latest Arch ISO:

		transmission-remote -a "https://archlinux.org/releng/releases/2024.09.01/torrent/"
	

Check download status:

		transmission-remote -l
	

Remove the torrent after download:

		transmission-remote -t all -r
	

Write Arch ISO to USB

Find USB device:

		for device in /sys/block/*
do
    if udevadm info --query=property --path=$device | grep -q ^ID_BUS=usb
    then
        echo $device
    fi
done
	

Write ISO to USB:

		sudo dd bs=4M if=~/Downloads/Torrents/archlinux-2024.09.01-x86_64.iso of=/dev/sdX conv=fsync oflag=direct status=progress
	

Boot Arch Installer ISO

  1. Plug in USB and start server
  2. Enter BIOS (F2 on boot) and select boot menu
  3. Choose Arch USB Flash Drive
  4. Select "Arch Linux Install Medium" as primary boot device

Connect to WiFi (if no ethernet)

		iwctl station list
iwctl station $STATION get-networks
iwctl station $STATION connect $SSID --passphrase $PASSWORD
	

Archinstall

Run archinstall:

		archinstall
	

Configuration (adjust to your needs):

  • Mirrors: Mirror Region → United States
  • Disk Configuration: Partitioning → best-effort → Select OS drive → filesystem: ext4 → separate home partition: yes
  • Hostname: archnas
  • User Account: Add a user
    • Username: archie
    • Setup password with pass manager: pass generate archnas/archie
    • Superuser (sudo): yes
  • Profile: Type → Minimal
  • Kernels: linux-lts
  • Additional Packages: git nano
  • Network Configuration: Copy ISO
  • Timezone: US/Eastern
  • Chroot: no
  • Reboot and remove install medium

Setup SSH Access

		# On the Server
sudo pacman -S openssh
sudo systemctl start sshd
sudo systemctl enable sshd
 
# On the client
ssh archie@<server_ip>
	

TODO: Add 2FA (reference) or disable SSH after setup

Install YAY Package Helper

		sudo pacman -S --needed git base-devel
git clone https://aur.archlinux.org/yay.git
cd yay
makepkg -si
cd ..
rm -rf yay
	

Additional Software

Install unzip utility:

		sudo pacman -S unzip
	

Setup Drive Pool with MergerFS

Installation

		sudo pacman -S fuse --noconfirm
yay -S mergerfs --noconfirm
	

Find Disk IDs

Device mappings can change, so use disk IDs:

		ls -al /dev/disk/by-id
	

Look for partition IDs (lines ending in -part1):

		ata-WDC_WD100EFAX-68LHPN0_2YK7S0GD-part1 (sdd1) (disk1)
ata-WDC_WD100EFAX-68LHPN0_JEK9DP8X-part1 (sda1) (disk2)
ata-WDC_WD101EFAX-68LDBN0_VCGN5M2P-part1 (sdc1) (disk3)
ata-WDC_WD100EFAX-68LHPN0_JEKX6VUZ-part1 (sdb1) (disk4)
ata-HUH721212ALE601_8HGL36YH-part1 (sdf1) (parity)

	

Update /etc/fstab

Important: Parity disk must be equal or larger than your biggest data disk

		# Data drives
/dev/disk/by-id/ata-WDC_WD100EFAX-68LHPN0_2YK7S0GD-part1 /mnt/disk1 ext4 defaults 0 0
/dev/disk/by-id/ata-WDC_WD100EFAX-68LHPN0_JEK9DP8X-part1 /mnt/disk2 ext4 defaults 0 0
/dev/disk/by-id/ata-WDC_WD100EFAX-68LHPN0_JEKX6VUZ-part1 /mnt/disk3 ext4 defaults 0 0
 
# Parity drive
/dev/disk/by-id/ata-WDC_WD101EFAX-68LDBN0_VCGN5M2P-part1 /mnt/parity1 ext4 defaults 0 0
 
# MergerFS pool
/mnt/disk* /mnt/storage fuse.mergerfs defaults,dropcacheonclose=true,allow_other,minfreespace=25G,fsname=mergerfs 0 0
	

Test Configuration

Save and reboot. Create a test file in /mnt/storage and verify it appears in one of the /mnt/diskX directories.

Setup SnapRAID

SnapRAID provides parity protection against drive failures.

Caveats:

  • Parity drive must match or exceed size of largest data disk
  • Parity is calculated on-demand via snapraid sync (not real-time)

Installation

		yay -S snapraid --noconfirm
	

Configure SnapRAID

Create/edit /etc/snapraid.conf:

		# Parity storage (must NOT be on a data disk)
parity /mnt/parity1/snapraid.parity
 
# Content files (need one copy per parity file plus one)
# Each must be on a different disk
content /var/snapraid.content
content /mnt/parity1/.snapraid.content
content /mnt/disk1/.snapraid.content
content /mnt/disk2/.snapraid.content
 
# Data disks (order is important for parity!)
disk d1 /mnt/disk1
disk d2 /mnt/disk2
 
# Exclusions
exclude /lost+found/
exclude .snapraid.content
exclude *.db-wal
	

Initial Sync

Run your first sync (this will take a while):

		sudo snapraid sync
	

Automation with snapraid-runner

Clone snapraid-runner:

		sudo git clone https://github.com/Chronial/snapraid-runner.git /opt/snapraid-runner
	

Create config at /etc/snapraid-runner.conf:

		[snapraid]
executable = /usr/bin/snapraid
config = /etc/snapraid.conf
deletethreshold = -1
touch = false
 
[logging]
file = /var/log/snapraid.log
maxsize = 5000
 
[email]
sendon = success,error
short = true
subject = [SnapRAID] Status Report
from = {your_email}
to = {your_email}
maxsize = 500
 
[smtp]
host = {smtp_host}
port = {smtp_port}
ssl = {true/false}
tls = {true/false}
user = {smtp_user}
password = {smtp_password}
 
[scrub]
enabled = true
percentage = 22
older-than = 12
	

Schedule with Cron

Run SnapRAID weekly (Sundays at 12:00):

		sudo pacman -S cronie
sudo EDITOR=nano crontab -e
	

Add:

		0 12 * * 0 python /opt/snapraid-runner/snapraid-runner.py --conf /etc/snapraid-runner.conf

	

Test Drives Before Use

Always test drives before adding to your array, especially used drives:

S.M.A.R.T. Check:

		sudo pacman -S smartmontools
sudo smartctl -H /dev/sdX
	

Bad Blocks Check: See badblocks documentation

mkfs Bad Blocks: See mkfs.ext3 documentation

Container Setup with Docker

Install Docker

		sudo pacman -S docker docker-compose --noconfirm
sudo systemctl start docker.service
sudo systemctl enable docker.service
reboot
	

Test installation:

		sudo docker run --rm hello-world
	

Portainer (Docker Web UI)

Manage containers through a clean web interface:

		docker volume create portainer_data
docker run -d --name=portainer 
    -e PGID=1000 
    -e PUID=1000 
    -p 8000:8000 
    -p 9000:9000 
    --restart=unless-stopped 
    -v /var/run/docker.sock:/var/run/docker.sock 
    -v portainer_data:/data 
    -v /mnt/storage/configs:/configs 
    portainer/portainer-ce
	

Access at http://<server_ip>:9000

Docker flags explained:

  • -d: Run in background (detached)
  • --name: Container name
  • -e PGID/PUID: User/group IDs
  • -p: Port mapping (host:container)
  • --restart: Auto-restart policy
  • -v: Volume mounts (host:container)

Watchtower (Auto-Updates)

Automatically update containers weekly:

		docker run -d --name=watchtower 
    -e PGID=1000 
    -e PUID=1000 
    --restart=unless-stopped 
    -v /var/run/docker.sock:/var/run/docker.sock 
    containrrr/watchtower 
    --schedule "0 0 4 * * *" 
    --cleanup
	

To disable auto-update for specific containers:

		--label=com.centurylinklabs.watchtower.enable=false
	

Samba (Network File Sharing)

Create Folder Structure

		cd /mnt/storage
mkdir configs private public
 
# Private/config permissions (owner only)
sudo chmod -R 770 configs private
sudo setfacl -R -d -m g::rwx configs private
sudo setfacl -R -d -m o::- configs private
 
# Public permissions (everyone)
sudo chmod -R 777 public
sudo setfacl -R -d -m g::rwx public
sudo setfacl -R -d -m o::rwx public
	

Deploy Samba Container

In Portainer, create a new stack named "samba":

		version: '3.9'
 
services:
    samba:
        image: dperson/samba
        container_name: samba
        restart: unless-stopped
        environment:
            - 'TZ=America/New_York'
            - 'USERID=1000'
            - 'GROUPID=1000'
            - 'SHARE1=public;/mount/public;yes;no;yes;all'
        volumes:
            - '/mnt/storage:/mount'
        ports:
            - "139:139/tcp"
            - "445:445/tcp"
        network_mode: bridge
        stdin_open: true
        tty: true
	

Access Samba Share

In file managers like Dolphin:

		smb://192.168.1.237/

	

TODO: Setup private access authentication

Jellyfin (Media Server)

Self-hosted alternative to Plex and Emby.

In Portainer, create a new container:

  • Name: Jellyfin
  • Image: jellyfin/jellyfin:latest
  • Ports:
    • 8096:8096 (HTTP interface)
    • 7359:7359/UDP (Local discovery)
    • 1900:1900/UDP (DLNA)
  • Volumes:
    • /mnt/storage/configs/jellyfin:/config
    • /mnt/storage/public/video/television:/data/television
    • /mnt/storage/public/video/movies:/data/movies
    • /mnt/storage/public/video/documentaries:/data/documentaries
    • /mnt/storage/public/video/channels:/data/channels
  • Environment:
    • PUID=1000
    • PGID=1000
    • TZ=America/New_York
  • Restart: Unless stopped

Access at http://<server_ip>:8096

References

Nextcloud (Private Cloud)

In Portainer, create a new container:

  • Name: Nextcloud
  • Image: lscr.io/linuxserver/nextcloud:latest
  • Ports:
    • 443:443
  • Volumes:
    • /mnt/storage/configs/nextcloud/config:/config
    • /mnt/storage/Nextcloud/data:/data
  • Environment:
    • PUID=1000
    • PGID=1000
    • TZ=America/New_York
  • Restart: Unless stopped

Access at https://<server_ip>:443

Docker Compose Alternative

		nextcloud:
    image: lscr.io/linuxserver/nextcloud:latest
    container_name: nextcloud
    environment:
        - PUID=1000
        - PGID=1000
        - TZ=Etc/UTC
    volumes:
        - /mnt/storage/configs/nextcloud/config:/config
        - /mnt/storage/Nextcloud/data:/data
    ports:
        - 443:443
    healthcheck:
        test: curl --insecure --fail "https://localhost:443/login" || exit 1
        interval: 2m
        retries: 10
        start_period: 10m
        timeout: 5s
    restart: unless-stopped
	

Additional Services to Explore

  • PhotoPrism/Immich: Self-hosted photo management (Immich docs)
  • Gitea: Self-hosted Git server
  • Homepage: Beautiful dashboard for your services (Homepage docs)

Conclusion

You now have a fully functional Arch Linux homelab with:

  • Unified storage pool with hot-swappable drives
  • Parity protection against drive failures
  • Automated backups and maintenance
  • Network file sharing
  • Self-hosted media streaming
  • Private cloud storage
  • Containerized service architecture
  • Automatic updates

Your homelab can now rival commercial cloud services while giving you complete control over your data and privacy.

References