Streaming is Broken III: The Foundation (OS, Docker, and "Day One")
Now that we have our hardware, we can begin with the system configuration of our server. The MiniPC I bought came with Windows installed. It’s also possible to use Microsoft’s system for what we want, but it’s a ridiculous waste of performance and energy.
A robust Home Server is not a desktop computer that stays on all day: it’s a dedicated system. We will build the software foundation from scratch, transforming the mini PC into an enterprise-level infrastructure, ready to run 24/7 without human intervention.
Choosing and Configuring the Operating System
But… why not Windows? Windows consumes about 2GB to 4GB of RAM just to exist and run its graphical interface. On our N100, that is a waste, as I said, it’s throwing performance in the trash. Furthermore, Docker on Windows (via WSL2) adds a layer of virtualization that can impact I/O and network performance.
Don’t use Windows. Forget this crap.
Here we can use any Linux… without an interface. A server doesn’t need a mouse, windows, or wallpapers.
We will install Ubuntu Server without a graphical interface. The server will be controlled 100% via terminal (SSH) from your main PC. This ensures every CPU cycle and megabyte of RAM is dedicated to what matters: your services.
Of course, installing Ubuntu from scratch will add some layers of complexity (not too many). Updating repositories, installing dependencies, configuring user permissions, editing system files… That’s where things can go wrong and errors appear that cost you dearly months later.
For this, I made a small script that will automate all these initial configurations. The code is self-explanatory.
#!/bin/bash
if [ "$EUID" -ne 0 ]; then
echo "Please run as root (sudo ./install.sh)"
exit 1
fi
REAL_USER=$SUDO_USER
if [ -z "$REAL_USER" ]; then
echo "Could not detect sudo user. Exiting."
exit 1
fi
USER_UID=$(id -u $REAL_USER)
USER_GID=$(id -g $REAL_USER)
USER_HOME=$(getent passwd $REAL_USER | cut -d: -f6)
BASE_DIR="$USER_HOME/EmuleVision"
echo "Starting installation for user: $REAL_USER"
echo "Base Directory: $BASE_DIR"
apt update && apt upgrade -y
apt install -y curl gnupg ca-certificates lsb-release cifs-utils samba vim git net-tools intel-media-va-driver-non-free libmfx1 vainfo
if ! command -v docker &> /dev/null; then
mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
usermod -aG docker $REAL_USER
fi
usermod -aG render $REAL_USER
usermod -aG video $REAL_USER
mkdir -p $BASE_DIR/{downloads/torrents,media/{movies,tv,manga},books}
mkdir -p $BASE_DIR/Settings/{Qbittorrent,Prowlarr,Sonarr,Radarr,Jellyfin,Gluetun,Portainer,Homepage,Scrutiny,AdGuard,Kaizoku,Kavita,CalibreWeb,Filebrowser}
chown -R $REAL_USER:$REAL_USER $BASE_DIR
chmod -R 775 $BASE_DIR
cat <<EOF >> /etc/sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
sysctl -p
sed -r -i.orig 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf
sed -r -i 's/#?DNS=/DNS=1.1.1.1 8.8.8.8/g' /etc/systemd/resolved.conf
rm -f /etc/resolv.conf
ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
systemctl restart systemd-resolved
cat <<EOF >> /etc/samba/smb.conf
[EmuleVision]
path = $BASE_DIR
writeable = yes
guest ok = no
create mask = 0775
directory mask = 0775
valid users = $REAL_USER
force user = $REAL_USER
EOF
echo "Set SMB password for $REAL_USER:"
smbpasswd -a $REAL_USER
systemctl restart smbd
TZ=$(timedatectl show -p Timezone --value)
cat <<EOF > $BASE_DIR/.env
PUID=$USER_UID
PGID=$USER_GID
TZ=$TZ
VPN_USER=vpn_username_here
VPN_PASS=vpn_password_here
EOF
chown $REAL_USER:$REAL_USER $BASE_DIR/.env
echo "Installation complete."
echo "1. Edit $BASE_DIR/.env with your VPN credentials."
echo "2. Place your docker-compose.yml in $BASE_DIR."
echo "3. Reboot the system."
What does the script do?
-
Native Docker. Installs the Docker Engine directly from the official source, ensuring the latest and most secure version, without relying on the distribution’s outdated packages. Trying to do this on Windows with WSL is asking for a headache.
-
Intel QuickSync Drivers. Ubuntu Server comes with generic drivers, which will directly affect the performance of our N100 for video transcoding. The script installs the intel-media-va-driver-non-free and configures the rendering permissions (render group), ensuring that Jellyfin has direct access to the hardware.
-
IPv6 Removal. Later I will focus specifically on the security part, since in my case I chose to protect my homeserver with a VPN. This is optional, but it guarantees your server’s privacy, especially if you plan to “acquire” movies or series via alternative methods. The script disables IPv6 directly in the Linux Kernel (sysctl.conf). This way, neither Docker, the VPN, nor qBittorrent can use IPv6, physically eliminating the chance of a leak. Remember, IP leaks via IPv6 are the number one security failure in home VPNs.
-
Creation of Folders and Initial Structure. The script creates the directory tree
/data/mediaand/data/downloadswith the correct user permissions (PUID/PGID). If this is done wrong now, Hardlinks (instant copies without duplicating space) will fail. We will talk more about this type of copy in the next post.
With this, our server is ready, secure, and optimized in 2 minutes!

PS: I called this project EmuleVision. Change it to whatever you want.
Configuring a Static IP (The Right Way)
Our server needs a Static IP, that’s obvious. If the IP keeps changing with every restart, our television will lose the reference to our server with every IP change made by the router’s DHCP. There are two ways to do this: the wrong way and the right way.
-
The wrong way. Setting a static IP only on the OS. Basically, this would mean configuring the static IP within Ubuntu’s network file (netplan). This works fine until you change your router or take your server to another house. Then you’ll have to connect a keyboard and HDMI to reconfigure it manually. Remember: we don’t want a graphical interface on our server.
-
The right way. We change the DHCP Reservation/Static Lease configuration directly on the router, without touching our OS. We configure the router to always assign the same IP (e.g., 192.168.1.50) to the server’s MAC Address. This way the server remains in “Automatic” (DHCP) mode. If you change networks, it grabs a new IP and works immediately. The management is centralized on the router.
To Summarize
After the physical installation (only power and network cables) and the OS, the workflow is surgical:
-
Access the router and find out the IP the server grabbed. With this, we identify our MiniPC’s MAC Address.
-
Assign a static IP (or another one you want) on the router (Static Lease).
-
Access it via SSH (
ssh user@ip) from your main computer. -
Run the
install.shscript. -
Reboot to apply the Kernel locks and Drivers.
In the next post, we will cover the server’s privacy and security. It’s optional, but as they say, every dog knows its own tail. In other words, protect your server!
