Self-Hosted Portfolio Site — niklabs.org
Project Type
Personal infrastructure project — self-hosted static portfolio site running on Proxmox homelab, served through Nginx Proxy Manager, published publicly via Cloudflare DNS and SSL.
Project Overview
This project covers the end-to-end setup of niklabs.org — a self-hosted portfolio and documentation site built with MkDocs Material, running on an Ubuntu Server inside the NikLabs Proxmox homelab, and published publicly through Cloudflare with SSL.
The goal was to build a professional-looking technical portfolio site that:
- Runs entirely on existing homelab infrastructure at zero additional cost
- Uses markdown files as the content source — no CMS, no database
- Looks like a modern documentation site (dark mode, search, code copy buttons)
- Is publicly accessible via a custom domain with valid SSL
- Can be updated by editing a markdown file and running one build command
Architecture
Visitor → Cloudflare (DNS + SSL) → Home Public IP
→ Router (Port 80 forward) → Nginx Proxy Manager (Docker)
→ Ubuntu Server LXC (Python HTTP server :8085)
→ MkDocs Static Site (/var/www/niklabs-site/site/)
Component Summary
| Component | Role |
|---|---|
| MkDocs Material | Builds static HTML site from markdown files |
| Ubuntu Server (Proxmox LXC) | Hosts the built static site |
| Python HTTP server | Serves static files on port 8085 |
| Systemd service | Keeps Python server running on boot |
| Nginx Proxy Manager | Reverse proxy — routes niklabs.org to Ubuntu server |
| Cloudflare | DNS, SSL termination, DDoS protection |
| niklabs.org | Custom domain |
Environment
| Attribute | Value |
|---|---|
| Hosting Platform | Proxmox homelab |
| Server | Ubuntu 22.04 LXC (k3s-worker2) |
| Server IP | 192.168.1.246 |
| Site Port | 8085 |
| Static Site Generator | MkDocs Material 9.x |
| Python Version | 3.12 |
| Reverse Proxy | Nginx Proxy Manager (Docker container) |
| DNS / SSL | Cloudflare (Flexible SSL mode) |
| Domain | niklabs.org |
Implementation
1. Install MkDocs on Ubuntu Server
apt update && apt upgrade -y
apt install python3 python3-pip -y
pip3 install mkdocs-material --break-system-packages
mkdocs --version
2. Create the MkDocs Project
Project structure created:
3. Configure mkdocs.yml
site_name: NikLabs
site_url: https://niklabs.org
site_author: Nikhil Seth
site_description: IT Infrastructure & Cloud Portfolio
theme:
name: material
palette:
- scheme: slate
primary: blue
accent: cyan
toggle:
icon: material/weather-sunny
name: Switch to light mode
- scheme: default
primary: blue
accent: cyan
toggle:
icon: material/weather-night
name: Switch to dark mode
features:
- navigation.tabs
- navigation.sections
- navigation.top
- navigation.indexes
- search.highlight
- search.suggest
- content.code.copy
- toc.follow
nav:
- Home: index.md
- Projects:
- projects/index.md
- Hyper-V Failover Cluster: projects/hyperv-cluster.md
- Homelab:
- homelab/index.md
- About: about.md
markdown_extensions:
- admonition
- pymdownx.superfences
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.tabbed:
alternate_style: true
- pymdownx.details
- attr_list
- tables
- toc:
permalink: true
extra:
social:
- icon: fontawesome/brands/github
link: https://github.com/Nikhilse120
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/nikhil-seth-9a798350/
4. Create Content Pages
Pages created:
docs/index.md— Homepagedocs/about.md— About / certifications / skillsdocs/projects/index.md— Projects listingdocs/projects/hyperv-cluster.md— First project write-updocs/homelab/index.md— Homelab overview
5. Build the Static Site
Output goes to site/ folder — this is the deployable static HTML.
6. Create Systemd Service
To keep the site running permanently across reboots:
[Unit]
Description=NikLabs MkDocs Site
After=network.target
[Service]
WorkingDirectory=/var/www/niklabs-site/site
ExecStart=python3 -m http.server 8085
Restart=always
User=root
[Install]
WantedBy=multi-user.target
Verified running:
7. Configure Nginx Proxy Manager
In NPM web UI → Hosts → Proxy Hosts → Add Proxy Host:
| Field | Value |
|---|---|
| Domain Names | niklabs.org |
| Scheme | http |
| Forward Hostname / IP | 192.168.1.246 |
| Forward Port | 8085 |
| Cache Assets | On |
| Block Common Exploits | On |
8. Cloudflare DNS Configuration
Added DNS records in Cloudflare dashboard:
| Type | Name | Content | Proxy |
|---|---|---|---|
| A | @ | HOME-PUBLIC-IP | Proxied ON |
| A | www | HOME-PUBLIC-IP | Proxied ON |
SSL/TLS settings:
| Setting | Value |
|---|---|
| SSL Mode | Flexible |
| Always Use HTTPS | ON |
| Minimum TLS Version | TLS 1.2 |
| Automatic HTTPS Rewrites | ON |
9. Router Port Forward
Forwarded port 80 on home router → NPM container host IP.
NPM was already handling port 80 for other homelab services — added niklabs.org as an additional proxy host.
Update Workflow
Every time a new project or page is added:
# 1. Edit or add markdown file
nano /var/www/niklabs-site/docs/projects/new-project.md
# 2. Add to nav in mkdocs.yml
nano /var/www/niklabs-site/mkdocs.yml
# 3. Rebuild and restart
cd /var/www/niklabs-site
mkdocs build
systemctl restart niklabs
Site updates in under 10 seconds.
Troubleshooting
Browser showing "page cannot be displayed"
- Browser was forcing HTTPS on a plain HTTP server
- Fix: access via
http://nothttps://for local testing - Cloudflare handles HTTPS termination for public access
Port already in use on first run
- Manually ran
python3 -m http.server 8085before creating the systemd service - Port was already bound when systemd tried to start
- Fix: killed the manual process, let systemd manage it exclusively
mkdocs.yml nav mismatch
- File was named
hyperv-cluster-mkdocs.mdbut nav referencedhyperv-cluster.md - Fix: renamed file to match nav entry
Key Learnings
- MkDocs Material produces a professional documentation site with zero frontend development
- Python's built-in HTTP server is sufficient for serving static files in a homelab context
- Systemd services are the correct way to keep processes running across reboots
- Cloudflare Flexible SSL mode allows plain HTTP on the origin server while providing HTTPS to visitors
- Nginx Proxy Manager simplifies routing multiple domains/services without touching raw Nginx config
- The entire stack costs nothing beyond the domain — all software is free and runs on existing hardware
Tools & Technologies
| Category | Technology |
|---|---|
| Static Site Generator | MkDocs Material 9.x |
| Content Format | Markdown (.md) |
| Web Server | Python HTTP server (built-in) |
| Process Manager | Systemd |
| Reverse Proxy | Nginx Proxy Manager (Docker) |
| DNS & SSL | Cloudflare |
| Hosting Platform | Proxmox LXC (Ubuntu 22.04) |
| Domain | niklabs.org |
Outcome
A fully self-hosted portfolio site running at niklabs.org with:
- Dark/light mode toggle
- Full-text search across all pages
- Code blocks with copy buttons
- Left navigation sidebar
- Right-side table of contents per page
- Mobile responsive
- Valid SSL via Cloudflare
- Zero ongoing hosting cost
- Update workflow under 10 seconds
Built and hosted entirely on the NikLabs homelab — Proxmox, Ubuntu, Docker, and Cloudflare.