Porthole is a native Windows desktop dashboard for WSL Containers.
It uses a WinUI 3 application for the UI and a tray-hosted backend for container and image operations. The app and tray communicate over named pipes using typed JSON contracts in Porthole.Core.
Implemented:
- ✅ Dashboard: real-time system metrics and container status
- ✅ Images: pull, tag, and delete container images
- ✅ Containers: start, stop, and remove containers
- ✅ Sessions: create and manage isolated session environments for workload grouping
- ✅ Networking: configure network mode (bridge vs. consomme) and inspect active port bindings and host proxy configuration
- ✅ Run Wizard: interactive container creation with template save/load, port mapping, environment variables, and volume configuration
Planned:
- 📋 Volume Management: track named volumes and virtiofs mounts with create/delete/prune operations
- 🔒 Enterprise Governance: MDM registry allowlists, Defender for Endpoint integration, audit logging
Real-time overview of system status and container inventory:
- Active container count and status breakdown
- System resource utilization
- Quick-access container controls
Manage container images in the active session:
- Pull: fetch images from registries with progress tracking
- Tag: apply custom repository and tag labels
- Delete: remove images (with dependency checks)
- Prune: clean up unused images
Lifecycle management for running containers:
- Start/Stop: manage container state
- Remove: delete containers (with safety confirmation)
- Inspect: view container details (ID, image, status, ports)
- Logs: view recent container output (future)
Inspect and configure container networking in the active session:
Network Mode Toggle
- Bridge: Containers are connected to a default bridge network (standard Docker mode)
- Consomme: Experimental mode for specialized networking scenarios
Active Port Bindings
- Real-time display of all port mappings from running containers
- Shows host port, container port, and protocol (tcp/udp)
- Auto-discovered via
wslc inspect— no manual configuration needed - Useful for debugging port conflicts and validating expose declarations
Proxy Configuration
- Reads host Windows proxy settings from environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY)
- Displays effective proxy configuration for container operations
- Helps diagnose proxy-dependent workloads (artifact downloads, registry access, etc.)
Isolated container environments for multi-tenant and workload grouping scenarios:
Session Lifecycle
- Create Session: Launch a new isolated session with a custom name (auto-generates storage directory)
- Active Session: All image and container operations target the active session context
- Switch Active Session: Designate which session is the active context (containers persist in their session)
- Delete Session: Remove an inactive session and all its containers (safety confirmation required)
Session Listing
- View all available sessions with metadata (name, storage path, active status)
- Visual indicator for the currently active session
- Storage path shows where session state and containers are persisted
Interactive guided flow for creating a container configuration and starting it:
Wizard Start
- Choose Use Template File to load a saved JSON template
- Choose Create New to start from an empty configuration
Configuration Steps
- Step 1: Basic settings (container name, image, optional startup command)
- Step 2: Advanced settings (port mappings, environment variables, volume mounts)
- Step 3: Review and run
Run Actions
- Primary action: Save Template and Run
- Secondary action: Run Without Saving from split-button menu
Template Format & Versioning
- Newly saved templates use a versioned envelope format:
{
"version": 2,
"container": {
"name": "web",
"imageReference": "nginx:latest",
"startupCommand": null,
"portMappings": ["8080:80"],
"environmentVariables": ["ENV=prod"],
"volumeMounts": ["C:\\data:/app/data"]
},
"savedAtUtc": "2026-07-05T11:42:52.0000000+00:00"
}- The loader supports:
- Legacy unversioned templates (raw container fields at root)
- Versioned templates (v1 and v2)
- Compatibility aliases for config payload location (
containerorconfig)
Default Template File Naming
- Save picker suggests:
porthole-<imagename>-ddmmyyhhss.json - Example:
porthole-nginx-0507261142.json
src/Porthole.App: WinUI 3 desktop dashboardsrc/Porthole.Core: shared models, service contracts, and viewmodelssrc/Porthole.Tray: tray host backend with WSL Containers integrationtests/Porthole.Core.Tests: unit tests
- Windows 10/11
- .NET SDK 8.0+
- WSL with WSL Containers (
wslc)
Porthole follows a three-tier architecture:
┌─────────────────────────┐
│ Porthole.App (WinUI) │ Desktop dashboard UI
│ (MainWindow, Pages) │ - MVVM with CommunityToolkit
└────────────┬────────────┘
│ Named Pipes (JSON IPC)
↓
┌─────────────────────────┐
│ Porthole.Core (Shared) │ Contracts & ViewModels
│ (Models, Services) │ - No UI, No SDK refs
└────────────┬────────────┘
│ Named Pipes (JSON IPC)
↓
┌─────────────────────────┐
│ Porthole.Tray (Backend) │ Container operations
│ (WslcBackendService) │ - WSL Containers SDK integration
└─────────────────────────┘
Key Design Principles:
- UI ↔ Backend Separation: All container operations flow through named pipes; no direct SDK calls from the dashboard
- Shared Contracts:
Porthole.Corecontains request/response DTOs and service interfaces that both app and tray implement/consume - MVVM with DI: Pages are thin; all business logic lives in ViewModels with relay commands
- Async-first: All I/O (pipes, file system, processes) is async to keep the UI responsive
dotnet build Porthole.slnx -c DebugTroubleshooting:
- If the tray process is locked:
Stop-Process -Name Porthole.Tray -Force - For a clean rebuild:
dotnet clean ; dotnet build Porthole.slnx -c Debug
Recommended: Run tray first (it auto-launches the dashboard)
dotnet run --project src/Porthole.Tray -c DebugThe tray host starts a named pipe server and automatically launches the dashboard. You can then reopen the dashboard by double-clicking the tray icon.
Alternative: Run components separately
Dashboard only (requires tray to be running):
dotnet run --project src/Porthole.AppTray only (without auto-launching dashboard):
dotnet run --project src/Porthole.Tray -c Debugdotnet test tests/Porthole.Core.Tests/Porthole.Core.Tests.csproj -c DebugPorthole includes a WiX v4 installer project intended for winget-friendly distribution.
dotnet build src/Porthole.Installer/Porthole.Installer.wixproj -c Release -p:ProductVersion=1.0.0 -p:Platform=x64What this does:
- Publishes framework-dependent (not self-contained)
Porthole.AppandPorthole.Traypayloads for the selected architecture - Stages payload files under
src/Porthole.Installer/obj/<Configuration>/payload - Produces an MSI named
Porthole-<version>-<arch>.msiundersrc/Porthole.Installer/bin/<arch>/<Configuration>
Winget notes:
- Use the MSI URL from GitHub Releases in your winget manifest (
InstallerType: msi) - Keep
PackageVersionaligned with-p:ProductVersionused for the MSI build - The MSI supports standard silent install switches used by winget (
/quietand/qn)
A GitHub Actions workflow runs tests on pull requests:
.github/workflows/tests.yml
Release automation documentation:
docs/release-workflow.md
- Define the model in
Porthole.Core/Models/(e.g.,MyFeatureSnapshot.cs) - Create the service interface in
Porthole.Core/Services/(e.g.,IMyFeatureService.cs) - Implement named pipe client in
Porthole.Core/Services/NamedPipe/NamedPipe<Feature>Service.cs - Create the ViewModel in
Porthole.Core/ViewModels/<Feature>ViewModel.cs - Implement backend logic in
Porthole.Tray/Services/WslcBackendService.cs - Add pipe operation handlers in
Porthole.Tray/Services/NamedPipeImageCatalogServer.cs - Create the Page in
Porthole.App/Pages/<Feature>Page.xaml[.cs] - Register in DI in
Porthole.App/App.xaml.cs - Add navigation in
Porthole.App/MainWindow.xaml[.cs]
Port bindings are discovered by:
- Getting list of running containers via
wslc list --all --format json - Filtering for State=2 (Running)
- For each container, calling
wslc inspect <container-name>to get full details - Parsing the
PortsJSON object (e.g.,"80/tcp": [{"HostPort": "8080"}]) - Building
PortBindingrecords with container name, host port, container port, and protocol
Sessions are stored in the WSL Containers SDK with:
SessionSettings sessionSettings = CreateDefaultSessionSettings(name)— generates new settingsSession session = new Session(sessionSettings)— creates session instancesession.Start()— initializes the session filesystem and runtime- Active session is tracked in
_activeSessionName(in-memory; resets on tray restart)