Skip to notes

Public Homelab Notes / 02

Proxmox

My virtualization host, the VMs it runs, and how those workloads recover.

4 notes · Current setups, past observations, and future plans stay as recorded.

Proxmox / Note 01

Proxmox Host

In this note

Primary Proxmox VE hypervisor running on the HP ProDesk 600 G4.

Homelab Overview | HP ProDesk 600 G4 | VM100 - Jellyfin | VM101 - OpenLabFrame Dev | VM102 - Management


Status

State: In use
Hostname: private / internal
Physical Host: HP ProDesk 600 G4
Network: Private LAN
Role: Primary virtualization host

Web Interface

Friendly URL:

<private Proxmox URL>

Direct LAN access:

<private service endpoint>

Physical Host

Proxmox runs directly on:

HP ProDesk 600 G4

Current host hardware:

ComponentSpecification
CPUIntel Core i7-8700
Cores / Threads6 cores / 12 threads
RAM32 GB
StorageSamsung 970 EVO Plus 1 TB NVMe
Integrated GPUIntel UHD 630
Host IPPrivate / internal

The Intel UHD 630 is used for hardware-accelerated media workloads through VM100 - Jellyfin.


Current Virtual Machines

VM100 - Jellyfin

Primary media-services VM.

Network: Private LAN

<private Jellyfin host>

Primary workloads include:

  • Jellyfin
  • Gluetun
  • ARR Stack
  • Docker support services
  • Portainer Agent
  • Beszel Agent

The Intel UHD 630 is made available to VM100 for Jellyfin Quick Sync hardware acceleration.


VM101 - OpenLabFrame Dev

Development VM used for Open Lab Frame work.

Network: Private LAN

<private development host>

VM101 is intentionally:

OFF when not actively developing

It is not an always-on production VM.

Primary role:

  • Website source editing
  • Build tooling
  • Production-build validation
  • Open Lab Frame development workflow

VM102 - Management

Always-on management VM.

Network: Private LAN

<private management host>

Primary services:

VM102 provides the reverse-proxy layer for internal .<private internal domain> services.


VM Operating Model

Current VM behavior:

Proxmox host
├── VM100 - Jellyfin
│   └── Always on
│
├── VM101 - OpenLabFrame Dev
│   └── On demand
│
└── VM102 - Management
    └── Always on

This keeps development workloads off when they are not required while preserving the core media and management infrastructure.


Certificates

The Proxmox web interface uses a trusted certificate for:

<private Proxmox hostname>

The certificate/private-key configuration was previously verified and restored successfully.

This included:

  • Verifying certificate/private-key compatibility
  • Restoring the trusted Proxmox certificate
  • Configuring ACME
  • Confirming browser access through the friendly hostname

Certificate troubleshooting should be documented separately in:

Certificate Recovery


SSH / Shell Access

Administrative work can be performed through:

  • Proxmox web interface → private node nameShell
  • SSH to the Proxmox host

The shell belongs to the physical hypervisor, not to VM100, VM101, or VM102.

Example prompt:

<admin-user>@<proxmox-host>:~#

This distinction is important when installing host-level tools such as the native Beszel agent.


Beszel

The Proxmox host runs the native Beszel binary agent.

It is not running Beszel through Docker.

Beszel displays:

  • CPU usage
  • Memory usage
  • Disk usage
  • Network activity
  • Load average
  • Host temperature
  • Uptime
  • systemd service count

Typical observed idle values:

CPU:        < 1%
Temperature: ~35–37 °C

Beszel has reported:

72 services
0 failed

Agent version observed:

0.20.0

Default agent listener:

<Beszel agent port>

The Beszel Hub itself runs on Raspberry Pi 5.


Uptime Kuma

Uptime Kuma independently monitors the Proxmox host.

Monitor name:

HP ProDesk - Proxmox

Target:

<private Proxmox host>

Current host-monitor settings:

Heartbeat Interval:       30 seconds
Retries:                  1
Heartbeat Retry Interval: 15 seconds
Global Timeout:           10 seconds

Alerts are sent through:

  • Discord
  • ntfy
  • iPhone notifications

This host monitor is intentionally more aggressive than normal application monitors because the VMs depend on the physical Proxmox host.


Reboot Recovery Test

A controlled host reboot was performed with:

sudo reboot

The purpose was to simulate a sudden infrastructure interruption and verify automatic recovery.

Result

The following returned automatically:

  • Proxmox
  • VM100
  • VM102
  • Docker on VM100
  • Docker on VM102
  • Jellyfin
  • Media-stack services
  • Nginx Proxy Manager
  • Portainer
  • Gluetun monitoring
  • Beszel agents
  • Uptime Kuma monitoring

VM101 remained off intentionally.

No manual VM or container startup was required.


Recovery Sequence

Observed design:

HP ProDesk boots
      ↓
Proxmox starts
      ↓
Host networking becomes available
      ↓
VM100 starts
VM102 starts
      ↓
Guest operating systems boot
      ↓
Docker starts
      ↓
Containers recover
      ↓
Uptime Kuma detects recovery
      ↓
iPhone / Discord notifications

This recovery test confirmed that the always-on infrastructure is capable of returning after a normal host reboot.


Why Proxmox Did Not Initially Alert During Reboot

The original Uptime Kuma monitor used:

Heartbeat Interval: 60 seconds
Retries:            2
Retry Interval:     60 seconds

The Proxmox host was able to reboot and become reachable again before Kuma exhausted the retry window.

The monitor was later changed to:

30-second interval
1 retry
15-second retry interval

to detect real host outages more quickly.


BIOS / Host Recovery Goals

Important BIOS goals for the Proxmox host include:

Fast Boot:                 Disabled
Intel Virtualization:      Enabled
VT-d / Directed I/O:       Enabled
Hyper-Threading:           Enabled
All CPU cores:             Enabled
Restore after AC loss:     Power On

The most important server-oriented setting is:

Restore after AC power loss → Power On

so a real power failure can recover like:

Power failure
     ↓
HP ProDesk shuts down
     ↓
Utility power returns
     ↓
ProDesk powers on automatically
     ↓
Proxmox starts
     ↓
VM100 + VM102 recover
     ↓
Services recover

VM100 GPU / Quick Sync

VM100 uses Intel UHD 630 hardware acceleration for Jellyfin.

The purpose is to reduce CPU load during media transcoding.

This is especially useful when a client cannot Direct Play because of:

  • Unsupported container
  • Unsupported audio codec
  • Unsupported subtitle codec
  • Bitrate limitation
  • Other client compatibility issues

See:

VM100 - Jellyfin


Backup / Restore

Proxmox recovery work has previously included restoring VM100 and rebuilding the surrounding service environment.

Important future documentation should cover:

  • VM backup locations
  • Backup schedules
  • VM restore process
  • Proxmox configuration backup
  • Certificate recovery
  • GPU passthrough recovery
  • VM startup ordering

Related notes:

VM Restore
Proxmox Recovery
Certificate Recovery


Monitoring Architecture

The current lightweight monitoring design is:

Uptime Kuma
→ Is the host/service reachable?
→ Alert when something breaks

Beszel
→ How much CPU/RAM/disk/network is being used?
→ Host temperature and load

This avoids relying on a large dashboard for routine checks.


Current Network Relationships

HP ProDesk
<private Proxmox host>
      ↓
Proxmox Proxmox host
      │
      ├── VM100
      │   └── <private Jellyfin host>
      │
      ├── VM101
      │   └── <private development host>
      │
      └── VM102
          └── <private management host>

Future Migration

The HP ProDesk is not intended to remain the main heavy-compute platform forever.

When NAS Compute Build is completed and validated, heavier workloads can be migrated gradually to the NAS / compute machine.

The HP ProDesk's later role is intentionally not finalized yet. The TP-Link Archer BE550 remains the planned primary router and Wi-Fi device.


Related Notes

Homelab Overview
HP ProDesk 600 G4
VM100 - Jellyfin
VM101 - OpenLabFrame Dev
VM102 - Management
Beszel
Uptime Kuma
Certificate Recovery
Proxmox Recovery
VM Restore
NAS Compute Build

Back to Proxmox

Proxmox / Note 02

VM100 - Jellyfin

In this note

Primary media-services VM running Jellyfin, the ARR Stack, download services, VPN routing, and related monitoring.

Homelab Overview | Proxmox Host | HP ProDesk 600 G4 | Jellyfin | Tailscale | Uptime Kuma | Beszel


Status

State: In use
VM ID: 100
Role: Primary media-services VM
Host: Proxmox Host
Network: Private LAN
Remote Network: Private Tailscale connectivity
Administrative account: omitted from public copy
SSH Alias: ssh <jellyfin-alias> Operation: Always on


Current Role

VM100 hosts the current media and acquisition stack.

Primary responsibilities include:

  • Jellyfin media server
  • VPN-isolated download traffic
  • Torrent downloading
  • Usenet downloading
  • Media automation
  • Subtitle automation
  • Request management
  • Remote Jellyfin access
  • Monitoring and alerts
  • Hardware-accelerated transcoding

Docker Services

Current major services include:

  • Jellyfin
  • Gluetun
  • ARR Stack
  • Portainer Agent
  • Beszel Agent

Media Stack

The media-automation layer is documented publicly only as:

ARR Stack

Public architecture:

Media Request / Automation
        ↓
     ARR Stack
        ↓
Completed Media
        ↓
     Jellyfin

Individual ARR Stack applications and internal workflow details are intentionally omitted from the public documentation.


Jellyfin

Jellyfin is the primary media server running on VM100.

Direct LAN endpoint:

<private service endpoint>

Remote/private access can use multiple paths depending on the client and network.


Jellyfin Network Paths

There are several valid connection paths.

Local Home Network

When a device is at home and using the Pi 3 DNS configuration:

iPhone / Client
      ↓
Pi 3 / Pi-hole DNS
      ↓
<private Jellyfin host>
      ↓
VM100 / Jellyfin

This keeps the traffic entirely inside the local network.

A Jellyfin activity log confirmed an iPhone connection from:

<private local client>

which proved the session was using the local LAN path.


Private Tailscale Path

When away from home and connected through Tailscale, a personal device can reach Jellyfin through the private network.

An observed iPhone Tailscale address was:

<private remote Tailscale client>

The VM100 Tailscale address is:

<private Jellyfin Tailscale endpoint>

This allows private remote access without relying on the public reverse-proxy path.


Public Remote Path

Friends and other remote users use the VPS / Caddy path.

Architecture:

Remote User
      ↓
Internet
      ↓
jellyfin.openlabframe.com
      ↓
VPS
      ↓
Caddy
      ↓
Tailscale
      ↓
<private Jellyfin Tailscale endpoint>:<SERVICE_PORT>
      ↓
VM100 / Jellyfin

The VPS Caddy configuration forwards Jellyfin directly to VM100 over Tailscale.

Conceptually:

jellyfin.openlabframe.com {
    reverse_proxy <private Jellyfin Tailscale endpoint>:<SERVICE_PORT>
}

Nginx Proxy Manager is not part of the public friend/Jellyfin path.


Why VPS Caddy Is Used

The VPS exists primarily because the home connection is behind Starlink CGNAT.

This prevents normal inbound port forwarding from being the preferred public-access method.

The VPS provides:

  • Publicly reachable endpoint
  • Reverse proxy
  • TLS termination
  • Stable ingress point
  • Tailscale path back to VM100

NPM vs Caddy

Public Jellyfin traffic uses:

Remote User
   ↓
VPS Caddy
   ↓
Tailscale
   ↓
VM100

It does not normally use:

VPS
   ↓
NPM
   ↓
Jellyfin

Avoiding an unnecessary NPM hop keeps the path simpler.

NPM remains focused primarily on private/internal homelab services.


Local DNS Override

Pi-hole can resolve:

jellyfin.openlabframe.com

directly to:

<private Jellyfin host>

for selected devices.

This allows the same Jellyfin hostname to use a local/direct path when the client is at home.

That avoids unnecessary traffic such as:

Home Client
   ↓
Internet
   ↓
VPS
   ↓
Tailscale
   ↓
Back Home

when the device could instead talk directly to VM100.


Direct Network Path vs Direct Play

These are separate concepts.

Direct network path
≠
Jellyfin Direct Play

Direct Network Path

Describes how the client reaches Jellyfin.

Examples:

<private local client> → <private Jellyfin host>

or:

Tailscale client → <private Jellyfin Tailscale endpoint>

Direct Play

Describes whether Jellyfin sends the original media file without converting it.

Jellyfin can use a direct network route while still transcoding the media.


iPhone Playback Testing

An iPhone 16 Pro Max was tested as a Jellyfin client.

The iPhone was capable of connecting directly over the local LAN.

However, one tested media file triggered transcoding.

Observed playback information:

Transcoding
28.1 Mbps
MP4
H264
FLAC

Reasons reported by Jellyfin:

The container is not supported
The subtitle codec is not supported

This confirmed that:

  • The network path was direct/local
  • The media itself was still being transcoded

The video codec was not necessarily the issue.

Container/audio/subtitle compatibility can independently trigger transcoding.


Hardware Acceleration

VM100 uses the Intel UHD 630 integrated GPU from the HP ProDesk 600 G4 for Jellyfin hardware acceleration.

Purpose:

  • Reduce CPU usage during transcoding
  • Accelerate supported encode/decode workloads
  • Improve simultaneous-stream capability
  • Reduce load on the i7-8700

The Intel iGPU / Quick Sync configuration was restored during the Proxmox recovery work.


Gluetun

Gluetun provides VPN connectivity for selected download-related services.

Its Docker health status is monitored.

A custom health-reporting workflow was created to report Gluetun status to Uptime Kuma.


Gluetun Health Script

VM100 contains:

/usr/local/sbin/check-gluetun-health

This script checks Gluetun's Docker health.

When healthy:

Gluetun
   ↓
Docker health = healthy
   ↓
VM100 sends Uptime Kuma heartbeat

If Gluetun becomes unhealthy:

Health check fails
   ↓
Heartbeat stops
   ↓
Uptime Kuma detects missed heartbeat
   ↓
DOWN alert

Gluetun Push Monitor

The Uptime Kuma Push URL is stored locally on VM100 at:

<PROTECTED_PUSH_URL_FILE>

Do not store the actual Push token in Obsidian.

The stored URL uses the Pi 5 LAN endpoint:

<private service endpoint>/api/push/<TOKEN>

not:

<private uptime URL>/...

The public hostname caused a redirect through Cloudflare Access.

The direct LAN endpoint returned:

HTTP 200

which confirmed the current Push token and LAN route were valid.


Gluetun Systemd Automation

The health checker is automated through systemd.

Service:

gluetun-kuma-check.service

Timer:

gluetun-kuma-check.timer

The timer runs the checker approximately every:

60 seconds

Expected timer state:

active (waiting)

Alert Flow

Current Gluetun alert architecture:

Gluetun Docker health
        ↓
VM100 health script
        ↓
systemd timer
        ↓
Uptime Kuma
        ↓
Discord
        +
ntfy
        ↓
iPhone

Alert Testing

The Gluetun alert chain was tested end-to-end.

Test method:

  1. Stop the heartbeat timer
  2. Leave Gluetun itself running
  3. Wait for Uptime Kuma to miss the heartbeat
  4. Confirm DOWN alert
  5. Restart timer
  6. Confirm recovery

Result:

DOWN notification → received on iPhone
UP notification   → received on iPhone

This validated the complete notification chain.


Uptime Kuma Monitoring

VM100 services currently monitored through Uptime Kuma include examples such as:

  • Jellyfin
  • Gluetun
  • ARR Stack
  • VM100 itself

Monitoring responsibilities:

Uptime Kuma
→ Is the service reachable?
→ Is the service responding?
→ Alert if it fails

Beszel

VM100 also runs a Docker-based Beszel agent.

Beszel provides resource visibility such as:

  • VM CPU usage
  • VM memory usage
  • Disk usage
  • Network activity
  • VM uptime
  • Docker container CPU usage
  • Docker container memory usage
  • Container status
  • Container health where configured

Docker Health vs Uptime Kuma

Beszel may display:

Health: None

for some containers.

This does not mean the service is broken.

It means the Docker container does not define a Docker HEALTHCHECK.

Example:

ARR Stack
Docker container: Running
Docker HEALTHCHECK: None
Uptime Kuma: Up

These are separate checks.


Containers With Docker Health Checks

Observed examples include:

Gluetun      → Healthy
ARR Stack  → Healthy
ARR Stack        → Healthy
ARR Stack   → Healthy

Some other containers show:

Health: None

while still operating correctly.


Resource Usage

Beszel has shown VM100 using approximately:

CPU:    < 1% while idle
Memory: ~37%
Disk:   ~29%

Values vary depending on:

  • Active downloads
  • Media scans
  • Jellyfin playback
  • Transcoding
  • ARR Stack activity
  • Usenet extraction
  • Torrent workload

ARR Stack

VM100 includes a private media-automation environment documented publicly only as ARR Stack.

It handles the automation layer around media acquisition, organization, and library preparation. Individual applications, download clients, and internal integration details are intentionally omitted from the public copy.

Uptime Kuma and Beszel provide high-level availability and resource visibility without exposing the component inventory.


Portainer Agent

VM100 runs the Portainer Agent.

This allows the central Portainer installation on VM102 - Management to view/manage VM100's Docker environment.


VM Recovery

VM100 was restored as part of the larger Proxmox recovery process.

The recovery included:

  • VM restoration
  • Jellyfin restoration
  • Docker/media stack restoration
  • Intel Quick Sync / UHD 630 acceleration
  • Tailscale connectivity
  • Remote Jellyfin access
  • Monitoring restoration

Proxmox Host Reboot Test

A controlled reboot of the physical Proxmox host was performed.

VM100 automatically returned after the Proxmox host rebooted.

The following recovered without manual startup:

  • VM100
  • Docker
  • Jellyfin
  • Media-stack services
  • Gluetun
  • Monitoring services

This confirmed the current automatic-start configuration is functioning.


Security / Design Rules

Important VM100 rules:

  • Do not expose unnecessary Docker ports publicly.
  • Keep public Jellyfin ingress through the VPS/Caddy architecture.
  • Use Tailscale for private remote connectivity.
  • Use Pi-hole local DNS for direct internal routing.
  • Do not store passwords, API keys, download-provider credentials, or Push tokens in Obsidian.
  • Keep VPN/download traffic isolated through the intended Gluetun design.

Monitoring Philosophy

For VM100:

Uptime Kuma
→ Availability
→ Alerts

Beszel
→ Resource usage
→ Docker visibility

The two systems complement each other rather than duplicate the exact same role.


Related Notes

Homelab Overview
Proxmox Host
HP ProDesk 600 G4
Jellyfin
Tailscale
Pi-hole
VPS Caddy
Nginx Proxy Manager
Uptime Kuma
Beszel
VM102 - Management

Back to Proxmox

Proxmox / Note 03

VM101 - OpenLabFrame Dev

In this note

On-demand development and production-build VM for the Open Lab Frame website.

Homelab Overview | Proxmox Host | HP ProDesk 600 G4 | Open Lab Frame | Nginx Proxy Manager | VM102 - Management


Status

State: On demand
VM ID: 101
Role: Open Lab Frame development / build environment
Host: Proxmox Host
Network: Private LAN
Administrative account: omitted from public copy
SSH Alias: ssh <development-alias> Operation: Powered on only when actively editing/building the website


Purpose

VM101 exists specifically to provide a clean Linux development environment for:

  • Open Lab Frame source editing
  • Git / GitHub work
  • Node.js build tooling
  • Production build generation
  • Selector hardening
  • JavaScript obfuscation
  • CSS processing
  • Build validation
  • Local development preview
  • Pre-deployment testing

VM101 is not intended to be an always-on production server.

When website work is finished, the VM is normally powered off.


Power Model

Normal state:

Not editing Open Lab Frame
        ↓
VM101 OFF

Development state:

Need to edit website
        ↓
Start VM101
        ↓
Develop / build / validate
        ↓
Finish work
        ↓
Shut VM101 down

This reduces unnecessary resource usage on the HP ProDesk 600 G4.


Network

LAN IP:

<private development host>

Development proxy hostname:

<private development URL>

Current Nginx Proxy Manager backend:

<private service endpoint>

Private DNS record:

<private development hostname>
    → <private management host>

<private management host> is VM102 - Management, which runs Nginx Proxy Manager.

The request path is:

Client
   ↓
Pi-hole DNS
   ↓
<private development hostname>
   ↓
<private management host>
   ↓
VM102 / NPM
   ↓
<private development host>:<SERVICE_PORT>
   ↓
VM101 development preview

Important Monitoring Rule

<private development hostname> should not be treated as a normal 24/7 service.

VM101 is intentionally powered off most of the time.

Therefore:

<private development hostname> offline

does not normally mean there is an outage.

Any Uptime Kuma monitor for VM101 or <private development hostname> should either:

  • Remain paused while VM101 is off
  • Be enabled only during development
  • Avoid sending normal critical outage alerts

This prevents false alerts for expected behavior.


Git Repository

Primary repository:

<private Open Lab Frame source repository>

Local repository:

<SOURCE_REPOSITORY>

Primary branch:

main

The repository has previously been verified as:

On branch main
Up to date with origin/main
Working tree clean

Readable source remains in Git rather than committing only minified production assets.


SSH Access

Windows SSH alias:

ssh <development-alias>

Windows SSH configuration uses:

HostName: <private development host>
Administrative account: <omitted>
Key: <SSH_KEY_FILE>

Example SSH config:

Host <development-alias>
    HostName <private development host>
    User <admin-user>
    IdentityFile <SSH_KEY_FILE>
    IdentitiesOnly yes

Development Runtime

The VM uses Node.js through nvm.

Previously verified versions:

nvm:  0.40.7
Node: v24.21.0
npm:  11.19.0

These versions represent the known working development environment at the time they were verified.


Dependency Installation

The project uses the lockfile-driven npm workflow.

Preferred dependency install:

cd <SOURCE_REPOSITORY>
npm ci

npm ci is preferred over a normal npm install for reproducible builds because it installs exactly what is specified in the lockfile.


Build Process

The production build is generated with:

npm run build

The build system produces the deployable:

dist/

directory.

Readable source remains separate from the production output.


Source Structure

The readable source baseline includes files such as:

src/
├── index.html
├── projects/
│   └── index.html
├── style.css
├── setup-menu.js
├── visitor-counter.js
├── owner-tools.js
└── history-modal.js

Static assets are stored separately and copied/processed into the production build.


Production Build Philosophy

Open Lab Frame uses a deliberate separation:

Readable private source
        ↓
npm run build
        ↓
Production processing
        ↓
dist/
        ↓
Hashed / minified / hardened assets

The goal is:

  • Keep source readable for development
  • Make production assets harder to casually copy/reuse
  • Keep filenames cache-friendly
  • Preserve deterministic builds
  • Avoid source maps in production
  • Maintain browser functionality

Selector Hardening

The production build includes deterministic selector hardening.

A custom build step rewrites project CSS classes and IDs.

Conceptually:

Readable source:
project-card
project-menu
history-modal

        ↓ build

Production:
x**********
y**********

The process preserves required third-party selectors such as Font Awesome classes.


Selector Hardening Details

Production selector hardening handles:

  • CSS classes
  • HTML classes
  • HTML IDs
  • ARIA relationships
  • JavaScript selectors

Supported JavaScript rewrites include:

getElementById
getElementsByClassName
querySelector
querySelectorAll
closest
matches

The readable files under src/ remain unchanged.


JavaScript Hardening

The production JavaScript build includes fragmentation and obfuscation.

The build generates multiple independent hashed JavaScript bundles with names similar to:

j-XXXXXXXX.min.js

Production goals include:

  • Multiple JS bundles
  • Meaningless hashed filenames
  • Obfuscated production code
  • Preserved execution order
  • No source maps
  • No obvious original module/function names in production output

CSS Build

Production CSS is also split/minified into hashed assets.

Example naming pattern:

c-XXXXXXXX.min.css

These assets are intended to be immutable and cacheable after deployment.


Hashed Assets

The production build uses content-hashed filenames for JavaScript and CSS.

Benefits:

  • Cache busting after changes
  • Long browser/CDN cache lifetimes
  • Old versions automatically become obsolete after filename changes
  • Production files are less descriptive than readable source filenames

Local Development Preview

VM101 provides the development preview backend through:

<private development host>:<SERVICE_PORT>

NPM exposes the friendly URL:

<private development URL>

This allows the development site to be viewed with a clean internal hostname rather than remembering an IP and port.


Development vs Production

VM101 is a development/build system.

The production Open Lab Frame website itself runs on Raspberry Pi 5.

Architecture:

VM101
Development / Build
        ↓
dist/
        ↓
Validated deployment package
        ↓
Pi 5
        ↓
Nginx
        ↓
Production Open Lab Frame

VM101 being powered off has no effect on the live website.


Production Host

The live website runs on:

Raspberry Pi 5

Production Nginx container:

ehworld

Production path:

Cloudflare
    ↓
Cloudflare Tunnel
    ↓
Pi 5
    ↓
Nginx / ehworld
    ↓
Open Lab Frame

This separation ensures:

Development VM offline
        ≠
Production website offline

Canonical Deployment Workflow

The preferred Open Lab Frame workflow is deliberate and staged.

General process:

Readable source
    ↓
Git / VM101
    ↓
npm ci
    ↓
npm run build
    ↓
dist/
    ↓
Local validation
    ↓
ZIP production output
    ↓
Transfer to Pi 5
    ↓
Integrity check
    ↓
Extract to staging
    ↓
Backup current production
    ↓
rsync dry run
    ↓
Deploy with rsync
    ↓
nginx -t
    ↓
Reload
    ↓
Public validation

Changes should not bypass this workflow casually.


Pi 5 Production Paths

Current production structure on Raspberry Pi 5 includes:

Active:
<PRODUCTION_SITE_DIRECTORY>

Stage:
<STAGING_DIRECTORY>

Backups:
<PRODUCTION_BACKUP_DIRECTORY>

Deployment Validation

Important checks include:

  • Build completes successfully
  • dist/ contains expected files
  • Production HTML references current hashes
  • CSS/JS selector hardening succeeded
  • No source maps were emitted
  • Local preview works
  • ZIP integrity passes
  • Staging extraction succeeds
  • rsync dry-run looks correct
  • Nginx configuration test succeeds
  • Public production site loads correctly

Nginx configuration validation:

docker exec ehworld nginx -t

Expected result:

syntax is ok
test is successful

Git Workflow

Development changes should normally follow:

Edit readable source
      ↓
Test
      ↓
Build
      ↓
Validate
      ↓
git status
      ↓
Commit
      ↓
Push

The private repository remains the authoritative readable source.


Important Build Rule

Do not manually edit the hardened/minified files under dist/ as the primary source.

Correct flow:

Edit src/
   ↓
Build
   ↓
Generate dist/

rather than:

Edit dist/ directly

Open Lab Frame Projects Page

The Projects page has evolved beyond the older placeholder state.

It now documents actual homelab device projects including:

  • Raspberry Pi 5
  • Raspberry Pi 3
  • HP ProDesk 600 G4
  • Jellyfin/media work
  • Monitoring screenshots
  • Infrastructure services

The old:

Under Development

Projects-page state is obsolete.


Search Engine Reindexing

Google Search Console was used to request re-indexing of:

https://openlabframe.com/projects/

because Google / Google AI continued reflecting an older version of the page.

Correct Search Console process:

URL Inspection
      ↓
https://openlabframe.com/projects/
      ↓
Test Live URL
      ↓
Request Indexing

The live Projects page was understood to:

  • Return HTTP 200
  • Be present in the sitemap
  • Not contain noindex
  • No longer contain the old "Under Development" content

Google Search and AI-generated answers may update on different timelines.


Security

VM101 should remain private.

Important rules:

  • SSH uses dedicated key authentication
  • Do not expose unnecessary VM101 ports publicly
  • Use NPM/private DNS for development access
  • Keep secrets out of the Git repository
  • Keep secrets out of Obsidian
  • Production credentials should not be hard-coded in source files
  • VM101 is not intended to become a public application server

Resource Strategy

Because VM101 is only needed during development, keeping it powered off when unused:

  • Reduces RAM usage on Proxmox
  • Reduces CPU overhead
  • Reduces unnecessary background services
  • Reduces attack surface
  • Keeps the Proxmox host focused on always-on workloads

Monitoring

VM101 does not require the same always-on monitoring treatment as VM100 or VM102.

Expected behavior:

VM101 off
→ Normal

<private development hostname> unavailable
→ Normal when not developing

This should not trigger routine critical alerts.


Relationship to VM102

VM102 - Management provides the private NPM reverse proxy for VM101.

<private development hostname>
        ↓
VM102 / NPM
        ↓
<private development host>:<SERVICE_PORT>
        ↓
VM101

NPM can remain online even while VM101 is powered off.


Relationship to Pi 5

VM101 builds Open Lab Frame.

The Raspberry Pi 5 hosts Open Lab Frame.

VM101
Development
   ↓
Build
   ↓
Deploy
   ↓
Pi 5
Production

This separation allows the development environment to be disposable/on-demand without affecting the production site.


Related Notes

Homelab Overview
Proxmox Host
HP ProDesk 600 G4
Open Lab Frame
Raspberry Pi 5
VM102 - Management
Nginx Proxy Manager
Network Topology

Back to Proxmox

Proxmox / Note 04

VM102 - Management

In this note

Always-on management VM hosting Nginx Proxy Manager, Portainer, and lightweight infrastructure tooling.

Homelab Overview | Proxmox Host | HP ProDesk 600 G4 | Nginx Proxy Manager | Beszel | Uptime Kuma


Status

State: In use
VM ID: 102
Role: Homelab management / reverse-proxy VM
Host: Proxmox Host
Hostname: private / internal
Network: Private LAN
Administrative account: omitted from public copy
Operation: Always on


Current Role

VM102 provides centralized management services for the homelab.

Its main responsibilities are:

  • Nginx Proxy Manager
  • Portainer
  • Internal reverse proxying
  • Friendly service hostnames
  • Docker management
  • Beszel resource monitoring

VM102 is intentionally lightweight and remains powered on continuously.


SSH Access

Windows SSH alias:

ssh <management-alias>

Windows SSH configuration:

Host <management-alias>
    HostName <private management host>
    User <admin-user>
    IdentityFile <SSH_KEY_FILE>
    IdentitiesOnly yes

Docker

VM102 runs Docker.

Primary management Compose project:

<MANAGEMENT_DIRECTORY>

Major containers include:

  • Nginx Proxy Manager
  • Portainer
  • Beszel Agent

Nginx Proxy Manager

Nginx Proxy Manager provides the internal reverse-proxy layer for homelab services.

Primary admin backend:

<private service endpoint>

Friendly internal hostname:

<private proxy-admin URL>

NPM itself is also proxied through NPM.


Portainer

Portainer provides centralized Docker management.

Direct backend:

<private service endpoint>

Friendly internal hostname:

<private container-admin URL>

Portainer also connects to remote Docker environments through Portainer Agents, including VM100.


Internal DNS Architecture

Most .<private internal domain> hostnames resolve through the Pi 3 / Pi-hole to:

<private management host>

which is VM102.

Nginx Proxy Manager then forwards traffic to the correct backend.

General design:

Client
   ↓
Pi 3 / Pi-hole DNS
   ↓
service.<private internal domain>
   ↓
<private management host>
   ↓
VM102 / NPM
   ↓
Backend service

Current Proxy Hosts

Current NPM entries include:

<private development hostname>
<private monitoring hostname>
<private proxy-admin hostname>
<private container-admin hostname>
<private Beszel hostname>

<private development hostname>

Backend:

<private service endpoint>

Target:

VM101 - OpenLabFrame Dev

This host is expected to be unavailable when VM101 is powered off.

That is normal behavior and should not generate critical always-on alerts.


<private monitoring hostname>

Backend:

<private service endpoint>

Target:

Raspberry Pi 5

This provides friendly access to Grafana.


<private proxy-admin hostname>

Backend:

<internal proxy endpoint>

This allows NPM to proxy its own admin interface through the internal hostname.

Friendly URL:

<private proxy-admin URL>

<private container-admin hostname>

Backend:

<internal container-admin endpoint>

Friendly URL:

<private container-admin URL>

Direct recovery access remains available through:

<private service endpoint>

This direct path is useful if NPM itself is unavailable.


<private Beszel hostname>

Backend:

<private service endpoint>

Target:

Beszel Hub on Raspberry Pi 5

Friendly URL:

<private Beszel URL>

SSL

NPM uses the existing wildcard certificate for:

<private internal wildcard>
<private internal domain>

Typical internal Proxy Host settings:

Force SSL:       ON
HTTP/2 Support:  ON
HSTS:            OFF
Cache Assets:    OFF
Websockets:      ON where needed

Publicly Accessible Setting

NPM Proxy Hosts may show:

Access List: Publicly Accessible

This does not automatically mean the service is public on the Internet.

It means NPM itself is not placing an additional NPM Access List in front of the Proxy Host.

Actual reachability still depends on:

  • DNS
  • Routing
  • Firewall rules
  • Tailscale
  • Whether ports are publicly exposed

Internal services remain part of the private homelab design.


NPM vs VPS Caddy

NPM is primarily for private/internal homelab services.

The public Jellyfin path does not use NPM.

Public Jellyfin path:

Remote User
   ↓
VPS Caddy
   ↓
Tailscale
   ↓
VM100 / Jellyfin

Internal service path:

Client
   ↓
Pi-hole DNS
   ↓
VM102 / NPM
   ↓
Internal backend

These are intentionally separate architectures.


Monitoring

VM102 is monitored through both Uptime Kuma and Beszel.


Uptime Kuma

Current VM102-related monitors include:

VM102 - Management
NPM - VM102
Portainer - VM102

VM102 Host Monitor

Type:

Ping

Target:

<private management host>

Purpose:

Is VM102 itself alive?

NPM Monitor

Target:

<private service endpoint>

Purpose:

Is Nginx Proxy Manager itself responding?

A normal healthy result:

HTTP 200

Portainer Monitor

Target:

<private service endpoint>

Purpose:

Is Portainer itself responding?

TLS verification can be ignored for this direct backend monitor because Portainer uses its own local HTTPS certificate.

Expected result:

HTTP 200

Why Separate Monitors

The monitors distinguish host failure from application failure.

Example:

VM102 - Management    UP
NPM - VM102           DOWN
Portainer - VM102     UP

Interpretation:

VM102 itself is alive
NPM specifically has failed

Whereas:

VM102 - Management    DOWN
NPM - VM102           DOWN
Portainer - VM102     DOWN

suggests:

VM102 itself is unavailable

Alerting

VM102 monitoring uses the same alert channels as the rest of the critical homelab:

  • Discord
  • ntfy
  • iPhone notifications

Beszel

VM102 runs a Docker-based Beszel agent.

Beszel provides quick visibility into:

  • CPU usage
  • Memory usage
  • Disk usage
  • Network activity
  • VM uptime
  • Container usage

Observed VM102 values have been approximately:

CPU:    < 1%
Memory: ~15–16%
Disk:   ~11%

This confirms that VM102 is a very lightweight management workload.


Portainer Role

Portainer is used to centralize Docker management.

VM102 hosts the main Portainer instance.

Other Docker systems can expose Portainer Agent endpoints for centralized visibility.

For example:

VM102 Portainer
      ↓
Portainer Agent
      ↓
VM100 Docker

Recovery Testing

A controlled reboot of the physical HP ProDesk 600 G4 / Proxmox Host host was performed.

During reboot:

VM102 - Management   DOWN
NPM - VM102          DOWN
Portainer - VM102    DOWN

After Proxmox returned:

  • VM102 started automatically
  • Docker started automatically
  • NPM recovered
  • Portainer recovered
  • Beszel Agent recovered
  • Uptime Kuma changed monitors back to Up
  • Recovery notifications were received

No manual service startup was required.


Recovery Behavior

Expected automatic sequence:

Proxmox starts
      ↓
VM102 starts
      ↓
Linux boots
      ↓
Docker starts
      ↓
NPM starts
Portainer starts
Beszel Agent starts
      ↓
Uptime Kuma confirms recovery

Security

VM102 is an administrative system.

Important rules:

  • Keep SSH key-only.
  • Do not publicly expose Portainer.
  • Do not publicly expose NPM admin access.
  • Keep private .<private internal domain> services behind internal DNS / Tailscale.
  • Do not store passwords or API secrets in Obsidian.
  • Use direct backend ports only when needed for recovery or monitoring.
  • Avoid adding extra authentication layers in front of applications if they interfere with native clients.

Relationship With Pi-hole

Raspberry Pi 3 / Pi-hole provides the local DNS records that point service hostnames to VM102.

Example:

      ↓
<private management host>

VM102 then decides which backend receives the request.


Relationship With Pi 5

Many VM102 Proxy Hosts point to services running on Raspberry Pi 5.

Examples:

<private monitoring hostname>
      ↓
<private Pi 5 host>:<SERVICE_PORT>

      ↓
<private Pi 5 host>:<SERVICE_PORT>

<private Beszel hostname>
      ↓
<private Pi 5 host>:<SERVICE_PORT>

So the Pi 5 hosts the application while VM102 provides the friendly HTTPS entry point.


Relationship With VM101

VM102 provides the reverse proxy for:

<private development hostname>

Backend:

<private development host>:<SERVICE_PORT>

When VM101 - OpenLabFrame Dev is off, the hostname is expected to fail.

That is not considered a VM102 failure.


Current Management Architecture

                    Pi-hole DNS
                         ↓
                  <private management host>
                         ↓
                  VM102 Management
                         ↓
              Nginx Proxy Manager
           ┌─────────────┼──────────────┐
           ↓             ↓              ↓
        Pi 5          VM101          VM102
      services       dev site      local apps

Related Notes

Homelab Overview
Proxmox Host
HP ProDesk 600 G4
VM100 - Jellyfin
VM101 - OpenLabFrame Dev
Nginx Proxy Manager
Portainer
Beszel
Uptime Kuma
Raspberry Pi 5
Raspberry Pi 3
Pi-hole

Back to Proxmox