Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

Home Lab — C# All the Way Down

[TO HIRE] Senior Solution Architect — Seeking team — 2026 — Full-Time Remote

I am working toward a reproducible development platform described and controlled from C#. The FrenchExDev ecosystem already contains libraries, typed CLI wrappers, configuration models and VM orchestration components. Bringing them together into a complete HomeLab remains integration work.

Anything-as-Code connects this work to Requirements-as-Code and Diem. Requirements-as-Code represents software intent and its verification relationships. Diem extends the approach to applications and their environment; the HomeLab is the infrastructure integration target. C# becomes the language for describing an instance, composing its modules and operating its services.

The Big Picture

The target is a self-hosted software development platform: GitLab as the forge, typed tool integrations, and a CI/CD path for building, testing and packaging the monorepo. Existing components make parts of that path expressible in C#. The complete instance model, coordinated lifecycle and end-to-end integration are still being developed.

The diagrams describe how the components are intended to fit together. The code snippets illustrate composition patterns, including proposed pipeline APIs; the Diem building-block references identify current source implementations. They do not establish that the complete platform is running.

Diagram
Existing libraries, wrappers and configuration models provide building blocks for this target architecture; the complete HomeLab integration remains in development.

Foundational Patterns: Result & Builder

Result and Builder provide reusable conventions for error handling and object construction across the ecosystem. Those conventions also inform the planned Lab composition layer.

Result — Explicit Error Handling Everywhere

Result<T> and Result<T, TError> make success and failure explicit in the APIs that use them. Consumers can inspect or compose the result through a shared vocabulary.

// BinaryWrapper uses Result throughout
Result<Reference<PodmanClient>> client = await new PodmanClientBuilder()
    .WithBinding(binding)
    .BuildAsync();

// Pipeline orchestration: chain operations that may fail
Result<BuildReport> report = await ParsePipeline(config)
    .Bind(pipeline => RunStages(pipeline))
    .Bind(stages => CollectArtifacts(stages))
    .Map(artifacts => GenerateReport(artifacts));

This matters as the monorepo grows. Scraper failures, parser errors and rejected options can use the same typed result channel. Match, Bind, Map and Recover make the handling visible to consumers and tools.

Builder — Typed Construction for Complex Objects

Builder<T> supports async object construction with validation accumulation, circular reference detection and SemaphoreSlim-based caching. The following pipeline example illustrates how that composition style could serve the integrated Lab.

// Building a GitLab pipeline configuration
var pipeline = await new PipelineConfigBuilder()
    .WithProject("FrenchExDev")
    .WithStages(stages => stages
        .Add(s => s.WithName("build").WithImage("mcr.microsoft.com/dotnet/sdk:10.0"))
        .Add(s => s.WithName("test").WithImage("mcr.microsoft.com/dotnet/sdk:10.0"))
        .Add(s => s.WithName("package").WithImage("mcr.microsoft.com/dotnet/sdk:10.0")))
    .WithArtifacts(a => a.WithExpiry(TimeSpan.FromDays(30)))
    .BuildAsync(ct);

Result and Builder work together: BuildAsync() returns Result<Reference<T>>, validation errors accumulate into the Result's failure channel, and the builder's Reference<T> handles lazy resolution for object graphs with circular dependencies.

These patterns provide reusable foundations. The Lab composition work builds on them alongside the existing wrappers and configuration models.

GitLab as the Forge

GitLab CE is the intended forge for the integrated HomeLab: source control, CI/CD pipelines, package registry and issue tracking. The target stack brings GitLab and its runners together through the container and configuration modules. Their coordinated provisioning and operation remain integration work.

GLab: C# Talking to GitLab

To orchestrate GitLab programmatically from C#, I scraped GLab (GitLab's official CLI) using BinaryWrapper — the same framework that powers the Podman (ouvre dans une nouvelle fenêtre), Podman Compose (ouvre dans une nouvelle fenêtre), Docker (ouvre dans une nouvelle fenêtre), Docker Compose (ouvre dans une nouvelle fenêtre), Packer (ouvre dans une nouvelle fenêtre), and Vagrant (ouvre dans une nouvelle fenêtre) wrappers.

[BinaryWrapper("glab", FlagPrefix = "--")]
public partial class GLabDescriptor;

// Full IntelliSense for every glab command
var client = GLab.Create(binding);

// Create a merge request
var cmd = client.MrCreate(mr => mr
    .WithTitle("feat: add quality gate to CI pipeline")
    .WithDescription("Adds QualityGate step after test stage")
    .WithSourceBranch("feature/quality-gate")
    .WithTargetBranch("main")
    .WithSquashBeforeMerge(true));

// Trigger a pipeline
var pipelineCmd = client.PipelineRun(p => p
    .WithBranch("main")
    .WithVariables(["DEPLOY_TARGET=staging"]));

// List project releases
var releasesCmd = client.ReleaseList(r => r
    .WithPerPage(10));

The same three-phase architecture applies: scrape GLab's --help across versions, generate typed commands and builders via Roslyn, execute with structured output parsing. GLab joins the wrapper family alongside Podman (58 versions, 180+ commands), Docker Compose (57 versions), Vagrant, and Packer.

This means C# can drive the entire GitLab workflow — creating projects, triggering pipelines, managing merge requests, publishing releases — with full type safety and version guards, not string concatenation against a REST API.

57 Projects Need CI/CD

The 57-project figure in this heading reflects an earlier snapshot of the monorepo. The enduring need is to coordinate builds, tests, quality checks and package delivery as the repository grows. Typed C# orchestration against a self-hosted GitLab is the Lab integration target; the source-to-package path below is the intended workflow.

The Pipeline Problem

Concern Need Target integration
Build Coordinate projects and shared package versions Central Package Management, incremental builds
Test Discover, execute and report relevant checks GitLab Runner, parallel test execution
Quality Evaluate complexity, coverage and mutation results QualityGate tool with per-project quality-gate.yml
Package Deliver versioned libraries GitLab Package Registry, versioned artifacts
Orchestration Pipeline definitions, triggers, dependencies GLab wrapper + C# pipeline configuration

From Source to Package

Diagram
Target source-to-package path for the integrated Lab. This diagram describes the intended orchestration, not a completed end-to-end run.

Quality Gates Close the Loop

The planned pipeline would evaluate per-project thresholds in quality-gate.yml, for example:

gates:
  complexity:
    cyclomatic_max: 15
    cognitive_max: 20
  coverage:
    line_min: 80
    branch_min: 70
  mutation:
    score_min: 60

The QualityGate tooling is intended to contribute analysis and report-based decisions to this path. Lab integration must connect the relevant analysis, coverage and mutation runs to the configured policy, preserve their results, and stop delivery when a required check fails. That complete pipeline behavior remains to be integrated and verified.

Package Delivery

In the target workflow, libraries accepted by the configured checks would be packed and pushed to GitLab's NuGet Package Registry. Consumers would retrieve versioned packages through standard NuGet feeds. The intended benefits are:

  • No external dependency on nuget.org for internal packages
  • Version control tied to the same GitLab instance that runs CI
  • Access control through GitLab's existing permission model

Infrastructure Stack

The infrastructure composition work draws on these tools and typed components. The table maps their intended roles in a HomeLab instance; it does not establish a deployed stack. The CLI wrappers use BinaryWrapper.

Layer Tool C# Wrapper
VM provisioning Vagrant FrenchExDev.Net.Vagrant (7 versions, custom parser, typed events)
Image building Packer FrenchExDev.Net.Packer (multi-version, HCL workflows)
Containers Podman FrenchExDev.Net.Podman (58 versions, 180+ commands, 18 groups)
Orchestration Docker Compose FrenchExDev.Net.DockerCompose (57 versions, 37 commands) + DockerCompose.Bundle (typed config from 32 schema versions)
Forge GitLab CE FrenchExDev.Net.GLab (scraped via BinaryWrapper)
Reverse proxy Traefik v3 Traefik.Bundle (typed static + dynamic config from JSON schemas) + PowerShell module (PoSh)

Typed Configuration: No More YAML by Hand

The CLI wrappers handle executing Docker Compose and Traefik — but what about the configuration files themselves? Writing docker-compose.yml or traefik.yml by hand means no type checking, no IntelliSense, and no compile-time validation. The Bundle projects solve this.

Both DockerCompose.Bundle and Traefik.Bundle follow the same Roslyn source generator pattern:

  1. Embed the official JSON schemas as AdditionalTexts
  2. Parse and merge them into a unified model (handling cross-version differences)
  3. Generate typed C# models + Builder classes for every configuration object

DockerCompose.Bundle ingests 32 versions of the official Compose spec (v1.0.9 to v2.10.1) and generates typed models for ComposeFile, ComposeService, networks, volumes, and every nested configuration object:

var compose = await new ComposeFileBuilder()
    .WithServices(services => services
        .Add("gitlab", s => s
            .WithImage("gitlab/gitlab-ce:latest")
            .WithPorts(["8080:80", "2222:22"])
            .WithVolumes(["gitlab-config:/etc/gitlab", "gitlab-data:/var/opt/gitlab"])
            .WithRestart("unless-stopped"))
        .Add("runner", s => s
            .WithImage("gitlab/gitlab-runner:latest")
            .WithVolumes(["/var/run/docker.sock:/var/run/docker.sock"])))
    .WithVolumes(v => v
        .Add("gitlab-config", vol => vol.WithDriver("local"))
        .Add("gitlab-data", vol => vol.WithDriver("local")))
    .BuildAsync(ct);

Traefik.Bundle reads the Traefik v3 JSON schemas (static configuration + file provider/dynamic configuration) and generates TraefikStaticConfig and TraefikDynamicConfig models with full builder support:

var dynamicConfig = await new TraefikDynamicConfigBuilder()
    .WithHttp(http => http
        .WithRouters(r => r
            .Add("gitlab", router => router
                .WithRule("Host(`gitlab.homelab.local`)")
                .WithService("gitlab-svc")
                .WithEntryPoints(["websecure"])
                .WithTls(tls => tls.WithCertResolver("letsencrypt"))))
        .WithServices(s => s
            .Add("gitlab-svc", svc => svc
                .WithLoadBalancer(lb => lb
                    .WithServers([new() { Url = "http://gitlab:80" }])))))
    .BuildAsync(ct);

The result is infrastructure configuration that can be discovered and composed through typed models and IDE completion. Compiler feedback checks the relationships expressed in those types. Configuration validation and execution checks are still needed for values, cross-service references and runtime behavior.

PowerShell Glue

While C# handles the heavy lifting, PowerShell modules provide the developer experience layer:

  • DevPoSh — Developer shell boilerplate: UTF-8, logging, module auto-loading, VS Code integration
  • InfraDev — Infrastructure orchestration cmdlets tying Vagrant, Packer, Docker Compose, and Traefik together
  • Claude.PoSh — Claude Code VM lifecycle management

Tech Stack

C# .NET 10 Roslyn Source Generators PowerShell 7 Podman Docker Compose Vagrant Packer GitLab CE GLab Traefik v3 Alpine Linux NuGet

The Point

The HomeLab is the integration target for Anything-as-Code applied to a development environment: a C# description, typed models, generated configuration and coordinated operations leading to reproducible infrastructure.

Result and Builder, BinaryWrapper, configuration Bundles and VM components provide existing building blocks. Diem brings the application and environment work toward a shared product experience. The next task is to compose those blocks and verify the complete lifecycle of an instance.

That integration will provide a concrete place to evaluate the ecosystem: provisioning, configuration, builds, tests and delivery exercised together. Follow the Diem presentation for the current direction and the HomeLab roadmap for the dependency-based sequence.


Existing C# and PowerShell components; an integrated, reproducible HomeLab in development.

⬇ Download