Skip to content

mosakrm0/self-hosted-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 

Repository files navigation

Home Lab from Scratch: Complete Server Documentation

Goal: Transform an old laptop into a fully functional 24/7 home server with no GUI, hosting Docker containers, accessible from anywhere in the world for free, without a static IP.


Table of Contents

  1. Chapter 1: Base Installation
  2. Chapter 2: Container Environment Setup
  3. Chapter 3: Deploying Core Services
  4. Chapter 4: Networking & Bypassing CGNAT

Chapter 1: Base Installation

Since the laptop is old, the main goal was to squeeze every drop out of those specs to serve applications, not waste them on a graphical desktop environment we'd never use anyway.

1. Preparing the Tools (Download & Burn)

Downloading the OS: We went to the official Debian website and downloaded Debian Netinst (Network Installer) — a lightweight image (~600MB) that only installs the bare essentials, pulling the rest from the internet during setup. Download from here: 'www.debian.org'

Burning the image: We used Rufus (or BalenaEtcher) on the main Windows machine to flash the ISO onto a USB drive.


2. Booting & Installing

  • Plugged the USB into the old laptop.
  • Powered it on and hit the Boot Menu key (usually F12 or F2), then selected boot from USB.
  • Started the installation. The first steps are standard:
    • Language: English (preferred for servers — logs stay readable)
    • Timezone: Jordan
    • Username: mosakrmov + password
    • Disk partitioning: Guided - use entire disk

3. The Golden Step: Headless Server Installation

This was the most critical moment of the entire install. When we reached the Software Selection screen:

Option Action
Debian desktop environment ❌ Uncheck
GNOME / Xfce ❌ Uncheck
SSH server ✅ Check
Standard system utilities ✅ Keep checked

Why? A desktop environment alone eats ~1.5GB of RAM out of our 4GB total. By skipping it, the OS now idles at under 200MB RAM, freeing over 75% of memory for actual applications.


4. Remote Login (The SSH Magic)

After installation completed and the system rebooted:

On the laptop (one time only): Log in and find the local IP:

ip a
# or
hostname -I
# Result: 192.168.100.86

And that was the last time we touched the old laptop. :)

From the main Windows machine, we opened PowerShell and typed:

ssh mosakrmov@192.168.100.86

From this point on, the old laptop became just a "server" sitting in the corner, and all work happens remotely from the main machine.


Pro Move: Prevent Sleep When Lid is Closed

Since it's a laptop, closing the lid puts it to sleep and kills the server. Fix:

sudo nano /etc/systemd/logind.conf

Find the line:

#HandleLidSwitch=suspend

Remove the # and change the value to:

HandleLidSwitch=ignore

Save the file (Ctrl+XYEnter), then apply:

sudo systemctl restart systemd-logind

Now you can close the lid, shove the laptop under the bed, and it runs 24/7.


Chapter 2: Container Environment Setup (Docker & Portainer)

After successfully SSHing into the server, the system was a clean slate consuming almost no resources. The next step was turning it into a modern container-based hosting environment.

1. Update the System & Install Essentials

Before anything else, update packages and install basic tools:

sudo apt update && sudo apt upgrade -y
sudo apt install curl git nano -y

2. Install Docker (The Heart of the Server)

Instead of a complex manual installation, we used Docker's official convenience script:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

Optional: Run Docker commands without sudo every time:

sudo usermod -aG docker mosakrmov
# Then log out and back in

3. Install Portainer (The Visual Control Panel)

Since the server has no GUI, managing dozens of containers purely through the terminal can get exhausting. Portainer provides a professional web-based GUI for managing Docker from any browser.

Create a volume to persist Portainer's settings:

sudo docker volume create portainer_data

Run the Portainer container:

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

Technical note: Mounting docker.sock inside the container allows Portainer to communicate directly with the Docker engine on the host system.


4. First Login

From the browser on the main machine:

http://192.168.100.86:9000

Create an admin account, and from this moment we can start, stop, and delete any container with a single click.


Chapter 3: Deploying Core Services

With a container environment ready, we started deploying the applications that make up our Home Lab.

1. Personal File Storage (FileBrowser)

Instead of uploading files through complex scp commands, we ran FileBrowser as our own personal Google Drive alternative.

The challenge: We hit a 403 Forbidden error when trying to upload files, caused by a permissions conflict between Linux and the container.

The fix: We used the hurlenko/filebrowser image and passed our system user identity (PUID=1000) to the container:

sudo docker run -d \
  --name filebrowser \
  --restart=always \
  -p 8080:8080 \
  -v /home/mosakrmov:/data \
  -v fb_config:/config \
  -e PUID=1000 \
  -e PGID=1000 \
  hurlenko/filebrowser

we can skip the auth if we want by adding --noauth after specifying the image

Access: http://192.168.100.86:8080


2. Book & Reference Library (Kavita)

As an engineer, I have a large collection of PDF references (engineering textbooks, Islamic studies volumes, etc.). Kavita is a fast, resource-light E-Reader platform:

sudo docker run -d \
  --name kavita_reader \
  --restart=always \
  -p 5001:5000 \
  -v /home/mosakrmov/movies:/data \
  -v kavita_config:/kavita/config \
  jvmilazz0/kavita:latest

Access: http://192.168.100.86:5001


3. Home Cinema (Jellyfin)

An open-source media server for organizing and streaming movies and TV shows to any screen or mobile device on the local network:

sudo docker run -d \
  --name jellyfin_cinema \
  --restart=always \
  -p 8096:8096 \
  -v /home/mosakrmov/movies:/media \
  -v jellyfin_config:/config \
  -v jellyfin_cache:/cache \
  jellyfin/jellyfin

Access: http://192.168.100.86:8096


4. Hosting Personal Dev Projects

Migrated personal projects off paid cloud hosting onto this server, Dockerize your project and run it in the server!


Chapter 4: Networking & Bypassing CGNAT

The biggest challenge for any home server: accessing it from outside the home (like from campus) without a Static IP — especially with CGNAT from ISPs, which makes traditional Port Forwarding impossible.

The Problem Visualized

[You at university]
        |
        | Want to reach...
        ↓
[Server at home]
  ← Hidden behind CGNAT
  ← No Static IP
  ← Port Forwarding not possible

1. Reverse Tunnels with Zrok

We used an open-source tool called Zrok. It opens a secure reverse tunnel from the local server to Zrok's global infrastructure, giving us a stable HTTPS-encrypted public URL.

Install Zrok on the server:

curl -sSLf https://get.zrok.io | bash
zrok enable <your-token>

Start the tunnel:

# Wrong — caused Connection Refused due to IPv6
zrok share public localhost:9000

# Correct — explicitly targets IPv4
zrok share public 127.0.0.1:9000

The smart fix: To avoid IPv6-related Connection Refused errors, we explicitly pointed Zrok at 127.0.0.1 instead of localhost.


2. Clean URLs for Free (Firebase Redirect Trick)

Zrok URLs are great, but they look random and are hard to remember — something like abc123xyz.share.zrok.io.

Instead of dealing with traditional DNS configuration or paying for a domain, we used a free Hacker Way trick:

Steps:

  1. Created a Firebase Hosting project (free tier) to get a clean URL:

    mosa-lab.web.app
    
  2. Configured firebase.json to instantly redirect to our Zrok URL:

    {
      "hosting": {
        "redirects": [
          {
            "source": "/**",
            "destination": "https://abc123xyz.share.zrok.io/:path*",
            "type": 301
          }
        ]
      }
    }
  3. Deployed:

    firebase deploy

The Final Result

[You at university]
        |
        | Type: mosa-lab.web.app
        ↓
[Firebase Hosting]
        |
        | Instant 301 Redirect
        ↓
[Zrok Tunnel — HTTPS encrypted]
        |
        | Reverse Tunnel
        ↓
[Your home server] 

All of this:

  • ✅ Zero cost
  • ✅ No static IP needed
  • ✅ No router configuration required
  • ✅ Automatic HTTPS encryption

📊 Services & Ports Summary

Service Port Description
Portainer 9000 Docker management web UI
FileBrowser 8080 Personal file storage
Kavita 5001 Book & PDF library
Jellyfin 8096 Media server (movies & shows)

Useful Day-to-Day Commands

# List all running containers
sudo docker ps

# Live resource usage
sudo docker stats

# Restart a specific container
sudo docker restart jellyfin_cinema

# View recent logs for a container
sudo docker logs filebrowser --tail 50

# Update a container to the latest image
sudo docker pull jellyfin/jellyfin
sudo docker stop jellyfin_cinema
sudo docker rm jellyfin_cinema
# Then re-run the original docker run command

About

A complete guide to turning an old laptop into a zero-cost Personal Cloud & Home Lab. Bypasses CGNAT using Zrok and Firebase for custom routing. Self-hosting Docker, Jellyfin, Kavita, and FileBrowser.

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors