One governed estate across Azure and our own hardware
Websites, business applications, AI connectors and home services, spread across a cloud subscription and a rack of small servers. We consolidated it into one platform where nothing deploys by hand.
At a glance
- 31
systems under management
- 20+
pipelines
- 3
self-hosted build agents
- 5,500+
commits across the 29 repositories we run
The challenge
The estate had grown the way estates do: a free-tier code host with no branch protection on private repos, containers deployed by hand, and long-lived secrets in more places than anyone would like. It served several businesses and a household, and one engineer ran it on a tight cloud budget.
The aim was a single platform with clear rules. Static sites stay static, steady compute runs on our own hardware, Azure holds identity and secrets, and every change goes through a pull request and a pipeline.
Architecture
Swipe to explore the diagram
Public sites are static and served from Azure. Everything with a runtime lives on our own hardware behind tunnels, deployed by pipelines that hold no stored secrets.
What we built
Azure
- Static Web Apps for brochure sites, with Bicep-declared custom domains and managed certificates
- A Container Apps environment with a shared registry and logging for code-running sites, parked when it wasn't earning its keep and restorable from a checklist
- Key Vaults per concern under RBAC with purge protection
Self-hosted
- A business server running the line-of-business apps and AI connectors in Docker, reachable only through Cloudflare tunnels
- A Proxmox host with VMs and an LXC, built from answer files and cloud-init
- A NAS for bulk storage and backup targets, with offsite cloud sync on top
Delivery and security
- Azure DevOps with branch policies on every repo and three self-hosted agents in separate business and personal pools
- A compose-over-SSH deploy template that ships images, health-checks and rolls back on failure, with its own pipeline to test the rollback
- Trivy on every pull request, before every image ships, and on a schedule that runs even when the code hasn't changed
Discipline
- Every state change on a host logged in a git-tracked change log, with the evidence that it worked
- One approved access path to every machine
- Repo history moved between code hosts with every branch and tag verified SHA for SHA
Engineering decisions
Static wherever it can be
A brochure site with no runtime needs goes on Static Web Apps. There's no cold start and nothing shared with other workloads.
Branch protection drove the move
The free-tier code host couldn't enforce pull-request-only main on private repos. Azure DevOps could, so every repo moved, with history verified on the way.
Deploys can't half-apply
Images ship first, tags flip, the health check runs, and anything unhealthy rolls back automatically. We test the rollback on purpose.
Scan on a schedule, not just on change
New CVEs arrive whether or not anyone commits. Scheduled scans run regardless.
Watch from somewhere else
Monitoring is designed to run off the hosts it watches, and business and personal CI never share a pool or a target.
Outcome
Every deployed app is on CI with no hand deploys, gated by environment locks and rollback-tested. Certificates renew themselves, and the whole estate is documented in one place.