paint-brush
Let's Build a Practical Home Lab for Learning and Experimentationby@morpheuslord
New Story

Let's Build a Practical Home Lab for Learning and Experimentation

by MorpheuslordDecember 23rd, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Home labs are systems or dedicated deployments made to learn and improve skills. I am doing this to learn about the different tech involved for a better and more powerful future build for my localized AI training and research use case. In this blog, I will share what I did and what I learned from this.
featured image - Let's Build a Practical Home Lab for Learning and Experimentation
Morpheuslord HackerNoon profile picture

Home labs are systems or dedicated deployments made to learn and improve skills, such as being a sysadmin, running various services to improve your Linux skills, or just tinkering around and discovering new things (this is my reason). Whatever your reason may be, this topic is very intriguing and can help you learn a lot. In this blog, I will share what I did and what I learned from this.


For this implementation I don't want an overkill enterprise-grade implementation that no one can replicate, what I want is a simple implementation that can be copied and used for simple tasks. I am doing this to learn about the different tech involved for a better and more powerful future build for my localized AI training and research use case.


In this implementation, I want to:


  • Host 6 major services which 5 I can access publicly.
  • In the 5 services I want:
    • A stable vitals monitoring deployment (Grafana & Prometheus).
    • A deployment software (Portainer) for on-the-go deployment.
    • A secure SSH connection.
    • A music streaming service (Jellyfin).
    • Finally an Image backup solution (PhotoPrism).
  • The last service is SMB and that is only for local connection and I don't want it to be exposed over the internet.


As you can see I only have 2 backup options which are SMB and PhotoPrism, I know there are several great backup options for me to deploy but as of now these 2 are more than enough and I have other ways of automating my backup tasks.

What I am using?

I am using an HP EliteDesk 800 G2 DM 35W with Intel Core I5 6500T and 16GB of RAM with a Geekbench single-core score of 1083 and multi-core score of 2898. It's not that powerful but powerful enough for my current goals. I bought this refurbished so I was able to get this for dirt cheap.

The Initial setup

For this implementation I used the Ubuntu server 24.04 LTS image, you can use the Windows server image or Red Hat instead. If you have never installed a Ubuntu server instance before you can check this great setup guild link. Once you have installed Ubuntu the next step will be installing docker, make sure you do NOT install docker from snap and instead install docker from the official docker repo, as Portainer has issues with the snap docker (no clue why).

Installing Docker

I don't have any specific reason to run even the docker installation on my own there are specialized OS for such deployments such as Unraid, truenas, etc. But all I want to do is test, I don't even have a raid setup in this system as this is probably an extra just-in-case copy of my data.


If you are replicating this then it would be wise to have 3 storage drives installed and have at least a RAID 1 configuration for the least possible guarantee of data security.


Docker install steps:

sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update


After the above command is completed execution you can run this command to install docker:

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin


Installing Portainer

Portainer will be our interface for managing docker deployments and also make it easy and intuitive for deploying new stacks of services.

  • Step 1: Create volume

        docker volume create portainer_data
    
  • Step 2: Run the deploy command

docker run -d \ 
	-p 8000:8000 \ 
	-p 9443:9443 \ 
	--name portainer \ 
	--restart=always \ 
	-v /var/run/docker.sock:/var/run/docker.sock \ 
	-v portainer_data:/data \ 
	portainer/portainer-ce:2.21.4

9443 and 8000 both can be used to interface locally with Portainer it won’t matter much as we will be using tunneling for the final public interfacing.

Creating an SMB share

SMB is for my large file backup storage, I will be accessing this only on my local network and not allowing it to be accessed publicly over the internet.


  • Step 1: Install samba
sudo apt update && sudo apt install samba -y
  • Step 2: Create a Share folder and apply permissions
sudo mkdir /mnt/sharefolder && sudo chmod 777 /mnt/sharefolder
  • Step 3: Edit the config file and add the new configurations with the appropriate username.
sudo nano /etc/samba/smb.conf
[smbshare]
	path = /mnt/sharefolder
	browseable = yes
	writable = no
	valid users = <<USERNAME>>
  • Step 4: Create an SMB user using the username set in the config file:
sudo smbpasswd -a <<username>>
sudo smbpasswd -e <<username>>
  • Step 5: Restart SMB service:
sudo systemctl restart smb
sudo systemctl enable smb

Installing Cloudflare zerotrust

Cloudflare zerotrust is a great way of running private services to the public internet without any issues or hassles with port-forwarding unless you running SMB or a VPN client which I was not able to get to work, regardless this system can allow you to route all your traffic in a very easy way. I won’t go into much detail about the initial setup and all for zero-trust you can check NetworkChuck’s videos for that: Link I installed the Cloudflare tunneling software via the dpkg package instead of a docker instance as it did not work with a docker instance (have no clue why? it’s probably me). The command I used:

curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb && 

sudo dpkg -i cloudflared.deb && 

sudo cloudflared service install <<USE THE TOKEN PROVIDED BY CLOUDFLARE>>

Cloudflare tunnel utilized port 7844 which is important for firewall implementation.

Configure the firewall

For my implementation I have taken a more simple and easy approach to firewall configuration, these are my firewall rules:

  • For Cloudflare we have 2 IPv4 and 2 IPv6 IP ranges to worry about so we can run these commands to allow access to only those trusted IP ranges access to our internal services:
sudo ufw allow from 198.41.192.0/24 to any port 7844 proto tcp
sudo ufw allow from 198.41.192.0/24 to any port 7844 proto udp
sudo ufw allow from 2606:4700:a0::/32 to any port 7844 proto tcp
sudo ufw allow from 2606:4700:a0::/32 to any port 7844 proto udp
sudo ufw allow from 198.41.200.0/24 to any port 7844 proto tcp
sudo ufw allow from 198.41.200.0/24 to any port 7844 proto udp
sudo ufw allow from 2606:4700:a8::/32 to any port 7844 proto tcp
sudo ufw allow from 2606:4700:a8::/32 to any port 7844 proto udp
  • For SSH and SMB I will make it easy on myself and make it accessible by anyone on my public network mainly the 2 local subnet ranges:
sudo ufw allow from 192.168.0.0/24 to any port 22 proto tcp
sudo ufw allow from 192.168.0.0/24 to any port 445 proto tcp
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
sudo ufw allow from 192.168.1.0/24 to any port 445 proto tcp
  • Enable the firewall and hope that you have not bricked your system:
sudo ufw enable

Now check all the connections and let’s get to the implementation of other services.

The Container Services

For this to be possible you need to create a folder with all the local bind folders created for the docker instances to store data in, mine is like this:

├── jelly
│   ├── config
│   ├── movies
│   ├── music
│   └── videos
├── photoprism
│   ├── database
│   ├── pictures
│   └── storage
└── prometheus
    └── prometheus.yml

Notice Grafana does not have any setup folder, that's because I have a volume defined for Grafana, and it’s just a dashboard to display vitals while Prometheus collects the data.

Jellyfin

The folders movies and videos are just for namesake you can use or leave it’s up to you, I won’t be explaining post-install steps as there are plenty of videos explaining that but I am showing the configs you can use to easily install it within Portainer.


The config I used:

services: 
	jellyfin: 
		image: linuxserver/jellyfin:latest 
		container_name: jellyfin 
		ports: 
			- "1900:1900" 
			- "7359:7359" 
			- "8096:8096" 
		volumes: 
			- "/home/deployment/jelly/config:/config"
			- "/home/deployment/jelly/videos:/data/Videos" 
			- "/home/deployment/jelly/movies:/data/Movies" 
			- "/home/deployment/jelly/music:/data/Music" 
		environment: 
		- PUID=1000  
		- PGID=1000  
		- TZ=Asia/Kolkata # Replace with your timezone restart: unless-stopped

The 8096 port is the main interfacing port which is important for later while creating tunnels.

PhotoPrism

PhotoPrism is a great media backup tool for Android and IOS alike and I found it interesting and wanted to try it out. Mainly because it’s free, local, great at indexing and I can re-download it on any device I want, great tool and extremely easy to set up. The following is the stack I used to implement this system.

services:
  photoprism:
    image: photoprism/photoprism:latest
    stop_grace_period: 10s
    depends_on:
      - mariadb
    security_opt:
      - seccomp:unconfined
      - apparmor:unconfined
    ports:
      - "2342:2342"
    environment:
      PHOTOPRISM_ADMIN_USER: "USERNAME"              # ENTER ADMIN USERNAME
      PHOTOPRISM_ADMIN_PASSWORD: "PASSWORD"          # ENTER ADMIN PASSWORD
      PHOTOPRISM_AUTH_MODE: "password"               
      PHOTOPRISM_SITE_URL: "http://localhost:2342/"  
      PHOTOPRISM_DISABLE_TLS: "false"                
      PHOTOPRISM_DEFAULT_TLS: "true"                 
      PHOTOPRISM_ORIGINALS_LIMIT: 5000               
      PHOTOPRISM_HTTP_COMPRESSION: "gzip"            
      PHOTOPRISM_LOG_LEVEL: "info"                   
      PHOTOPRISM_READONLY: "false"                   
      PHOTOPRISM_EXPERIMENTAL: "false"               
      PHOTOPRISM_DISABLE_CHOWN: "false"              
      PHOTOPRISM_DISABLE_WEBDAV: "false"             
      PHOTOPRISM_DISABLE_SETTINGS: "false"           
      PHOTOPRISM_DISABLE_TENSORFLOW: "false"         
      PHOTOPRISM_DISABLE_FACES: "false"              
      PHOTOPRISM_DISABLE_CLASSIFICATION: "false"     
      PHOTOPRISM_DISABLE_VECTORS: "false"            
      PHOTOPRISM_DISABLE_RAW: "false"                
      PHOTOPRISM_RAW_PRESETS: "false"                
      PHOTOPRISM_SIDECAR_YAML: "true"                
      PHOTOPRISM_BACKUP_ALBUMS: "true"               
      PHOTOPRISM_BACKUP_DATABASE: "true"             
      PHOTOPRISM_BACKUP_SCHEDULE: "daily"            
      PHOTOPRISM_INDEX_SCHEDULE: ""                  
      PHOTOPRISM_AUTO_INDEX: 300                     
      PHOTOPRISM_AUTO_IMPORT: -1                     
      PHOTOPRISM_DETECT_NSFW: "false"                
      PHOTOPRISM_UPLOAD_NSFW: "true"                 
      PHOTOPRISM_DATABASE_DRIVER: "mysql"            
      PHOTOPRISM_DATABASE_SERVER: "mariadb:3306"     
      PHOTOPRISM_DATABASE_NAME: "photoprism"         
      PHOTOPRISM_DATABASE_USER: "photoprism"        
      PHOTOPRISM_DATABASE_PASSWORD: "PASSWORD"       # ENTER DB PASSWORD
      PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
      PHOTOPRISM_SITE_DESCRIPTION: ""               
      PHOTOPRISM_SITE_AUTHOR: ""                    
    working_dir: "/photoprism" 
    
    
    volumes:
      - "/home/deployment/photoprism/pictures:/photoprism/originals"
      - "/home/deployment/photoprism/storage:/photoprism/storage"


  mariadb:
    image: mariadb:11
    restart: unless-stopped
    stop_grace_period: 5s
    security_opt:
      - seccomp:unconfined
      - apparmor:unconfined
    command: --innodb-buffer-pool-size=512M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
    volumes:
      - "/home/deployment/photoprism/database:/var/lib/mysql"
    environment:
      MARIADB_AUTO_UPGRADE: "1"
      MARIADB_INITDB_SKIP_TZINFO: "1"
      MARIADB_DATABASE: "photoprism"
      MARIADB_USER: "photoprism"
      MARIADB_PASSWORD: "PASSWORD" # ENTER DB ROOT PASSWORD
      MARIADB_ROOT_PASSWORD: "PASSWORD" # ENTER DB PASSWORD
  watchtower:
    restart: unless-stopped
    image: containrrr/watchtower
    profiles: ["update"]
    environment:
      WATCHTOWER_CLEANUP: "true"
      WATCHTOWER_POLL_INTERVAL: 7200
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "~/.docker/config.json:/config.json"

Also, port 2342 is important for the Cloudflare tunnel deployment.

Monitoring

I tried other services such as NetData but the lack of customization and complexity and not being able to use it with my tunneling system was a big downvote for me and that's why Prometheus and Grafana were a great choice. I will dig deeper into the customization in the dashboard I went with but as of now I will explain the collection agents I used:


  • node_exporter: I am using this to collect core system vitals from my host Linux machine, this is a great agent and gives me all the necessary information I need.
  • cadvisor: This is node_extractor but for my docker deployments I can monitor resource usage thanks to this and I think it’s a great choice.
volumes:
  prometheus-data:
    driver: local
  grafana-data:
    driver: local

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - "/home/deployment/prometheus:/config"
      - "prometheus-data:/prometheus"
    restart: unless-stopped
    command: 
      - "--config.file=/config/prometheus.yml"

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    volumes:
      - "grafana-data:/var/lib/grafana"
    restart: unless-stopped

  node_exporter:
    image: quay.io/prometheus/node-exporter:v1.8.2
    container_name: node_exporter
    command: "--path.rootfs=/host"
    pid: host
    restart: unless-stopped
    volumes:
      - "/:/host:ro,rslave"

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.51.0
    container_name: cadvisor
    ports:
      - 8080:8080
    volumes:
      - "/:/rootfs:ro"
      - "/run:/run:ro"
      - "/sys:/sys:ro"
      - "/var/lib/docker/:/var/lib/docker:ro"
      - "/dev/disk/:/dev/disk:ro"
    devices:
      - /dev/kmsg
    privileged: true
    restart: unless-stopped


Once both of the systems are running go to the Grafana link and set Prometheus as a data source for that use http://prometheus:9090/ for dashboards I used Node Exporter Full and Cadvisor Exporter, both of them have great layouts and give great visualization.


The final stack for all my deployments can be found on my gist link. Copy and modify it as much as you want.

The Cloudflare Tunnel

Now once we have all the deployments done we have one local IP of our machine let’s consider it as 192.168.1.75 and it as 5 ports to be mapped with Cloudflare, once you are done with all the pre-requisites make sure you follow the following in case you’re running the same set of services as mine:

  • Jellyfin: 192.168.1.75:8096 --> HTTP of Cloudflare tunnel
  • Portainer: 192.168.1.75:9443 --> HTTPS of Cloudflare tunnel
  • SSH: 192.168.1.75:22 --> SSH of Cloudflare tunnel
  • PhotoPrism: 192.168.1.75:2342 --> HTTP of Cloudflare tunnel
  • Grafana: 192.168.1.75:3000 --> HTTP of Cloudflare tunnel


SSH has a few more steps, which involve creating an application instance within Cloudflare zerotrust, To open the Access > Applications tab, now select the self-hosted option, then configure the new application with a name of your choice and point it to the same subdomain as the main SSH service is pointing. Now you can create custom rules for how you want the SSH service to be accessed, I chose the browser rendering option which can be found in the Additional settings menu.

My previous and current backup workflow

For my main backups, I am using Cryptomator to create an encrypted version of my important folders which contain all my credentials and personal information, once my encryption is completed my script automatically copies the files to the appropriate folders which later get synced with my proton drive, and my external SSD, for the automation I am using Microsoft Power Automate. As of now, I am using robocopy a Windows utility for incremental file and folder copying and as I have dedicated large-volume storage I can copy a large amount of data from my main system to this dedicated connection via SMB.


This is the robocopy script I am using:

@echo off
set LOGFILE=D:B\backup.log
set ROBOCOPY_OPTIONS=/E /MIR /XD "node_modules" /XF "*.tmp" "*.log" /Z /MT:16 /R:2 /W:1 /LOG:%LOGFILE% /TEE

:: Start the backup process for each folder
robocopy "D:\A" "Z:\A" %ROBOCOPY_OPTIONS%
robocopy "D:\B" "Z:\B" %ROBOCOPY_OPTIONS%
robocopy "D:\C" "Z:\C" %ROBOCOPY_OPTIONS%
robocopy "D:\D" "Z:\D" %ROBOCOPY_OPTIONS%
robocopy "D:\E" "Z:\E" %ROBOCOPY_OPTIONS%
robocopy "D:\F" "Z:\F" %ROBOCOPY_OPTIONS%
robocopy "D:\G" "Z:\G" %ROBOCOPY_OPTIONS%
robocopy "D:\H" "Z:\H" %ROBOCOPY_OPTIONS%
robocopy "D:\I" "Z:\I" %ROBOCOPY_OPTIONS%

echo Backup completed successfully. Log file: %LOGFILE%


Change this to match your needs.

My Improvement plans

As of now since I am testing I am planning to buy a TPU unit and check AI-related tasks and techniques, once I have everything planned I am planning to increase the scale of implementation and build a more robust and more powerful lab setup.


This was more of a knowledge dump kind of blog which can help me learn what I had done before to make my setup work if any of the above information was of use to you then feel free to use the information as you please and if you are curious about anything just ping me on LinkedIn or Twitter I will try my best to give you a clear response.