UnifyOps: Internal Developer Platform
A platform I built to deploy multi-service systems safely using GitOps, Kubernetes, and strict runtime isolation.
Problem and Constraints
I needed infrastructure that could deploy multiple services consistently, fail safely, and operate without my constant attention.
The alternative was manual deployments, inconsistent configurations across services, and debugging the same problems in different places. I had seen this pattern before — services that worked in isolation but failed in combination, deployments that required tribal knowledge, and outages that cascaded because nothing was isolated.
Constraints
Solo ownership.
I am the only contributor. This means no version negotiation between teams, but also no margin for complexity I cannot debug alone. Every abstraction must pay for itself in reduced cognitive load.
Bare-metal Kubernetes.
The platform runs on home server infrastructure, not managed cloud services. I operate the control plane, the storage layer, and the networking. This rules out cloud-native shortcuts but forces understanding of what actually runs.
Rapid iteration without downtime.
I deploy frequently. The system must handle failed deployments, bad migrations, and broken code without taking down working services. Rollback must be a Git revert, not a runbook.
System Map
UnifyOps spans three repositories with distinct responsibilities:
unifyops
The application monorepo. Contains all service code organized by domain and stack. Each stack implements a three-tier pattern: React frontend, FastAPI API gateway, FastAPI service with PostgreSQL. A shared Python package (unifyops_core) provides logging, authentication, and exception handling across all services.
unifyops-helm
The deployment contract. A single Helm chart that deploys any stack to Kubernetes. Services are not special-cased; they provide values, the chart provides structure. This chart defines health checks, network policies, resource limits, and migration ordering.
unifyops-infra
The GitOps source of truth. Contains environment-specific values files and ArgoCD ApplicationSets. The cluster state is derived from this repository. Deployments happen by updating an image tag and pushing.
This separation allows me to change application behavior, deployment mechanics, or environment configuration independently, without coupling unrelated concerns.
Runtime Architecture
Each stack runs as isolated pods in environment-scoped namespaces (uo-dev, uo-staging, uo-prod):
Traffic flows are enforced by NetworkPolicy, not convention. The service tier only accepts connections from its API gateway. PostgreSQL only accepts connections from its service. A compromised pod cannot reach adjacent services.
Deployments use sync-wave ordering: database ready (wave 0), migrations complete (wave 1), application pods start (wave 2). This guarantees schema changes apply before code that depends on them.
Platform Contracts
The platform relies on contracts between repositories. Some are explicit and validated; others are implicit and carry risk.
| Contract | Enforcement | Risk |
|---|---|---|
| Helm values schema | Template validation | Low |
| Health endpoints | Runtime probe | Medium |
| Port conventions (3000/8002/8001) | Convention only | Medium |
| Shared library version | None (wildcard) | High |
| Image naming | Convention across 3 repos | High |
| GitOps paths | ArgoCD sync failure | Low |
High-risk contracts are candidates for CI validation if this platform moved beyond solo ownership.
Core Package Design
unifyops_core centralizes cross-cutting concerns that would otherwise be duplicated across every service. It enforces consistency at the code level — not through documentation or convention, but through shared implementation.
| Module | Responsibility |
|---|---|
exceptions | Typed exception hierarchy with HTTP semantics |
logging | Structured JSON output, sensitive data redaction, service metadata injection |
auth | JWT validation middleware, service-to-service authentication, token caching |
client | Async HTTP client with logging, error handling, credential masking |
All services return identical error shapes:
{
"status_code": 404,
"message": "User not found",
"error_id": "550e8400-e29b-41d4-a716-446655440000",
"error_type": "db_record_not_found",
"timestamp": "2024-01-01T12:00:00.123456Z"
}
One line — register_exception_handlers(app) — gives every FastAPI app identical error responses. The error_id enables log correlation across services.
Monorepo Structure
A stack is a vertical slice of functionality that can be developed, deployed, and scaled independently.
unifyops/
├── identity/ # Domain: user identity
│ ├── auth/ # Stack: authentication
│ │ ├── auth-app/ # React frontend
│ │ ├── auth-api/ # FastAPI gateway
│ │ └── auth-service/ # FastAPI + PostgreSQL
│ └── user/ # Stack: user profiles
├── platform/ # Domain: platform infrastructure
│ └── environment/ # Stack: environment management
└── shared/
└── unifyops_core/ # Cross-stack Python utilitiesThe CI workflow detects which apps changed through path matching:
- Changing
auth-servicebuilds onlyauth-service - Three apps changed → three parallel build jobs
- SHA tags are immutable; environment-latest tags enable quick rollback
End-to-End Deploy Flow
A code change flows from commit to running pod in approximately two minutes:
| Time | Step | Actor |
|---|---|---|
| T+0s | Push to dev | Developer |
| T+5s | Change detection | GitHub Actions |
| T+30s | Docker build (cached) | GitHub Actions |
| T+45s | Push to Harbor | GitHub Actions |
| T+50s | Update infra repo | GitHub Actions |
| T+60s | ArgoCD detects drift | ArgoCD |
| T+70s | Migration job | Kubernetes |
| T+90s | Deployment rollout | Kubernetes |
| T+105s | Traffic serving | — |
Failure Containment
- Build fails: Zero blast radius — no image pushed, no cluster change
- Image pull fails: Old pod continues serving (
maxUnavailable: 0) - Migration fails: Deployment blocked, old pods serve with old schema
- Health check fails: New pod doesn't receive traffic
- Rollback: Revert image tag in values file, ArgoCD syncs within 3 minutes
Decisions and Tradeoffs
| Decision | Why | Cost | Revisit When |
|---|---|---|---|
| GitOps with ArgoCD | Audit trail, drift detection, self-healing | Additional infra repo | Never |
| Monorepo | Atomic commits, shared tooling | No per-service versioning | Multiple teams need independent releases |
| Centralized core package | Consistency without documentation | Services cannot deviate | Teams need autonomy |
| Three-tier stacks | Security boundaries via NetworkPolicy | Three deploys per stack | Extreme internal call volume |
| Default-deny network | Blast radius limitation | Explicit allow required | Never |
Architecture Overview
┌─────────────────────────────────────────────────────────────────────────┐
│ DEVELOPMENT │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ unifyops │────│ unifyops_core │ │
│ │ (monorepo) │ │ (shared package) │ │
│ └──────────┬──────────┘ └─────────────────────┘ │
└─────────────┼───────────────────────────────────────────────────────────┘
│ git push
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ BUILD │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ GitHub Actions │────────▶│ Harbor Registry │ │
│ │ (build & push) │ │ (images + charts) │ │
│ └──────────┬──────────┘ └─────────────────────┘ │
└─────────────┼───────────────────────────────────────────────────────────┘
│ update tag
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ GITOPS │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ unifyops-infra │───▶│ ArgoCD │◀───unifyops-helm │
│ │ (desired state) │ │ (reconcile) │ (templates) │
│ └─────────────────────┘ └──────────┬──────────┘ │
└─────────────────────────────────────────┼───────────────────────────────┘
│ sync
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ KUBERNETES CLUSTER │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Namespace: uo-{env} │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────────┐ ┌────────┐ │ │
│ │ │ app │────▶│ api │────▶│ service │────▶│ postgres│ │ │
│ │ │ :3000 │ │ :8002 │ │ :8001 │ │ :5432 │ │ │
│ │ └─────────┘ └─────────┘ └─────────────┘ └────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Unidirectional flow from monorepo to cluster. ArgoCD acts as the control plane, reconciling desired state from Git. unifyops_core ensures runtime consistency across all services.
Outcomes
- Deployment velocity: Code change to production traffic in under two minutes, with zero-downtime rolling updates
- Failure containment: Build failures have zero blast radius; runtime failures are isolated to the affected stack
- Operational consistency: All services log identically, return identical error shapes, and authenticate identically
- Auditability: Every deployment is a Git commit; every rollback is a revert
- Scalability: Adding a new stack is copying a template and updating configuration
These outcomes matter because the platform is designed to be operated by one person, safely, on infrastructure I fully control.
What I Would Improve Next
- Versioned core package releases: Publish to Harbor PyPI, pin explicit versions in services
- Contract validation in CI: Validate health endpoints, ports, and image naming before deployment
- Observability depth: Add OpenTelemetry instrumentation and Grafana dashboards
- Multi-cluster support: Extend ArgoCD for disaster recovery or geographic distribution
What I Owned
Full vertical ownership. I designed the architecture, wrote the code, built the CI/CD pipelines, configured ArgoCD, and operate the Kubernetes cluster.
Application Code
FastAPI services, React frontends, SQLAlchemy models, Alembic migrations
Shared Infrastructure
unifyops_core package (4,400 lines) — logging, auth, exceptions, HTTP client
CI/CD
GitHub Actions with change detection, matrix builds, Harbor push, GitOps updates
Kubernetes Deployment
Helm chart, sync-wave ordering, NetworkPolicy, health probes, HPA
GitOps
ArgoCD ApplicationSets, environment namespaces, sealed secrets
Operations
Monitoring, log aggregation, incident response, rollback execution
This is not a team project I contributed to. I built it.
UnifyOps is a platform I built to solve my own infrastructure problems. It enforces consistency through shared code, deploys through Git, contains failures by design, and operates without my constant attention.
Every decision reflects real constraints: solo ownership, bare-metal infrastructure, and the need for safe rapid iteration. I acknowledge what I traded away — flexibility, per-service versioning, and the ability to deviate from platform patterns.
This is how I build and operate software.