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 Software Developer — Seeking team — 2026 — Full-Time Remote

During my job search period, I'm building a complete development infrastructure in C# — from foundational patterns to CI/CD pipelines to deployment and monitoring — proving that a single developer with the right abstractions can manage serious complexity. This is the operational heart of the FrenchExDev ecosystem.

The Big Picture

The Home Lab is not a toy project. It's a self-hosted software development platform: GitLab as the forge, typed C# wrappers for every CLI tool in the stack, and a CI/CD pipeline that builds, tests, and packages the 57 projects in the FrenchExDev monorepo. Every layer is code. Every tool has a typed API.

Diagram

Foundational Patterns: Result & Builder

Every project in FrenchExDev builds on two foundational libraries. They aren't utilities — they're the grammar the entire ecosystem speaks.

Result — Explicit Error Handling Everywhere

Result<T> and Result<T, TError> replace exceptions as control flow. Every operation that can fail returns a Result. Every consumer must handle both paths. No silent swallowing, no surprises.

// 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 at scale. With 57 projects, error handling conventions must be mechanical, not cultural. If a scraper fails, if a parser hits an edge case, if a version guard rejects a flag — the error propagates through the same type-safe channel. Match, Bind, Map, Recover — the vocabulary is consistent from the lowest library to the highest application.

Builder — Typed Construction for Complex Objects

Builder<T> handles async object construction with validation accumulation, circular reference detection, and SemaphoreSlim-based caching. Every non-trivial object in the ecosystem is built through a Builder.

// 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 two patterns are the bedrock. Everything above — BinaryWrapper, CLI wrappers, pipeline orchestration — is built on this foundation.

GitLab as the Forge

The Home Lab runs GitLab CE as its software forge — source control, CI/CD pipelines, package registry, and issue tracking. GitLab is self-hosted on the infrastructure stack, provisioned through Podman containers managed by Docker Compose.

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, Podman Compose, Docker, Docker Compose, Packer, and Vagrant 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

A monorepo with 57 projects isn't viable without automated CI/CD. Each project needs to be built, tested, quality-gated, and — for libraries — packaged and published. Doing this manually is impossible. Doing it with bash scripts is fragile. Doing it with typed C# orchestration against a self-hosted GitLab is the point.

The Pipeline Problem

Concern Scale Solution
Build 57 .csproj files, shared Directory.Packages.props Central Package Management, incremental builds
Test 400+ tests across 4 test projects (BinaryWrapper alone) GitLab Runner, parallel test execution
Quality Cyclomatic complexity, coverage, mutation scores QualityGate tool with per-project quality-gate.yml
Package 15+ libraries need NuGet delivery GitLab Package Registry, versioned artifacts
Orchestration Pipeline definitions, triggers, dependencies GLab wrapper + C# pipeline configuration

From Source to Package

Diagram

Quality Gates Close the Loop

Each project defines thresholds in quality-gate.yml:

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

The QualityGate tool — itself part of the monorepo — runs Roslyn semantic analysis, ingests Cobertura coverage and Stryker mutation reports, and returns a pass/fail exit code. The pipeline stops if quality degrades. No exceptions, no overrides.

Package Delivery

Libraries that pass quality gates are packed and pushed to GitLab's built-in NuGet Package Registry. Downstream projects within the monorepo (and external consumers) pull versioned packages through standard NuGet feeds. The self-hosted registry means:

  • 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 physical infrastructure runs on the Home Lab hardware, provisioned through the same typed wrappers (all built on 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)
Forge GitLab CE FrenchExDev.Net.GLab (scraped via BinaryWrapper)
Reverse proxy Traefik PowerShell module (PoSh)

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 Alpine Linux NuGet

The Point

This Home Lab exists to answer a question: can a single developer, with the right patterns and tooling, build and maintain a professional-grade development platform?

The answer is yes — if you invest in foundations. Result and Builder give you a grammar for error handling and object construction that scales across 57 projects. BinaryWrapper gives you typed APIs for every CLI tool in your stack. GitLab gives you the forge. And C# ties it all together with compile-time safety.

The Home Lab is not the end goal — it's the proof that the ecosystem works. Every library, every wrapper, every pattern converges here: a self-hosted platform where FrenchExDev builds, tests, gates, and packages itself. The infrastructure-as-code philosophy drives every layer.


Built with .NET 10, Roslyn source generators, PowerShell 7, self-hosted GitLab CE, and the conviction that typed APIs beat string concatenation at any scale.