The App Wearing a Website's Clothes
Websites and apps used to be different things. They aren't anymore. The infrastructure industry hasn't caught up.
The distinction between a website and an application dissolved somewhere around 2015 and nobody held a funeral.
I don't mean this abstractly. I mean that the technical, organizational, and financial assumptions we built around the word "website" (that it's static, that it's cheap, that anyone can host it) stopped being true the moment a marketing page needed a database and a SaaS product needed to rank on Google. Today almost everything online is a hybrid: application logic wearing the skin of a web page, served from infrastructure that would have qualified as enterprise architecture a decade ago.
The industry that builds and operates these things hasn't fully absorbed the implication. We still price hosting like it's 2009. We still staff DevOps teams like the cloud solved the operations problem. We still talk about "migrating to the cloud" as though the destination is the hard part, when the hard part was always the wiring.
The Gap Nobody Talks About
AWS launched in 2006. Azure followed. Google Cloud after that. And their government partitions: GovCloud, Azure Government, Google's Assured Workloads. The cloud gave everyone the same primitives: compute, storage, networking, databases, CDNs, WAFs, monitoring. In theory, you could finally own the whole stack.
In practice, nobody ever built the cPanel for the cloud.
That observation has shaped most of what I've spent the last decade doing. Cloud providers sell building blocks. They do not sell a coherent way to wire those blocks together, deploy reliably, and manage the result over time without hiring a platform engineering team. The gap between "I have an AWS account" and "my application is running in production with monitoring, security, and automated deployments" is where the average enterprise cloud migration costs $1.2 million and takes 18 months. It's a gap filled with Terraform modules, Kubernetes manifests, CI/CD pipelines, IAM policies, and a thousand decisions that have nothing to do with the actual product but everything to do with whether it stays running.
The organizations that feel this the most are the ones that can least afford to staff around it. A museum shouldn't need a platform engineering team to run their website. A defense contractor shouldn't need a six-month procurement cycle to get a public-facing site into GovCloud. A university processing student financial data can't use shared hosting, but it also can't conjure a DevOps team from its IT budget.
This is the problem we built Rabbit to solve.
How Rabbit Actually Works
The pitch is simple: describe your infrastructure in YAML and let automation handle provisioning. The reality underneath is more interesting than the pitch.
Every YAML file maps to a production-grade Terraform module with sensible defaults. But the key design decision, the one that separates Rabbit from a Terraform wrapper, is the module weight system. Every module has a hardcoded integer weight that determines deployment order. DNS and SSL certificates deploy at weight 5–8. Cloud foundations (networking, GKE clusters, node pools, Cloud SQL) deploy at 10–40. Kubernetes primitives at 55–95. Application deployments at 100–120. Edge infrastructure like WAF and CloudFront at 125–130. Monitoring and testing at 140–160.
This matters because infrastructure has hard dependencies that can't be parallelized. A CloudFront distribution that references a Kubernetes service endpoint cannot be provisioned before the service exists. A WAF that protects a CloudFront distribution cannot be configured before the distribution has an ID. A monitoring integration can't watch a service that hasn't been created yet. The weight system encodes these relationships so you never have to think about them. Destroy runs in reverse order automatically.
Placeholders use #{Variable} syntax and resolve at config merge time, before Terraform runs. #{Environment} becomes production or staging. #{Namespace} derives from the repository name. #{GcpProject} pulls the GCP project ID. This is different from Terraform's native cross-module references. Rabbit uses static values, not dynamic output wiring. This is a deliberate trade-off. It's less flexible, but it means you can read a YAML file and know exactly what it will produce without tracing output chains through a dependency graph.
The pipeline itself runs as a GitHub Action inside a purpose-built container from the UDX Worker stack, specifically the R2A (Rabbit Automation Action) image. When you push a .rabbit/ config, the R2A container spins up, reads your YAML, runs terraform plan on a PR (posting the diff as a comment), and applies on merge. The whole flow is zero-credential: GitHub authenticates to GCP via OIDC Workload Identity Federation, which means no static keys, no service account JSON sitting in GitHub Secrets. (I wrote more about why this matters for multi-environment deployment orchestration.)
Deploying with Impunity
The phrase we use internally is "deploying with impunity." It means you can push frequently because a bad deploy cannot stick. The system detects and reverses it before it becomes an incident. Three things make this work.
First, the repository is the command center. A git push is the only deployment mechanism. No SSH into production. No manual kubectl apply. No console clicks. The git log is the audit trail. This sounds obvious until you realize how many organizations with nominal GitOps pipelines still have engineers SSHing into nodes to restart services or patch configs. We don't allow it because the moment you do, your git state and your live state diverge, and reconciliation becomes guesswork.
Second, self-validating deployments. Every deploy includes automated post-deploy validation: health checks, endpoint verification, response code validation. If the checks fail, the deployment is treated as failed and the previous known-good state is restored. No human intervention required.
Third, and the most distinctive piece, is what we call the Crypto Canary. A cryptographic token is embedded into each deployment artifact at build time. After the deploy completes, an automated check validates that the correct token is present and correct in the live environment. If the canary check fails (token missing, corrupted, or unreachable), automatic rollback triggers. The name riffs on the canary-in-a-coal-mine concept, but instead of manual health checks, it uses cryptographic proof that the artifact you intended to deploy is the artifact actually serving traffic.
The Crypto Canary also underpins cloud hopping, our pattern for moving infrastructure across cloud providers and regions. The canary validates that the migrated workload is healthy before the old instance is torn down, making cross-cloud migrations atomic and reversible. Migration stops being a project. It becomes a config change.
What the Compliance Frameworks Actually Need
Government and regulated industries need infrastructure that satisfies specific control families. CMMC 2.0 Level 2, NIST 800-171, FedRAMP. These aren't abstract compliance labels. They map to concrete technical controls, and the Rabbit architecture was designed to satisfy them natively rather than retroactively.
Zero-credential pipelines directly satisfy NIST 800-171 AC-2 and IA-2, the controls around credential management and least-privilege access. When your deployment pipeline authenticates via OIDC federation instead of stored keys, there are no credentials to rotate, leak, or compromise. The attack surface of the CI/CD layer drops to nearly zero.
Secrets management uses GCP Secret Manager with references injected at container runtime. Secrets are never baked into images or YAML configs. Every secret access produces an audit trail. This gives you SC-28 (protection of information at rest) and AU-2 (audit events) coverage without bolting on a separate secrets management product.
The WAF module ships with six managed rulesets by default: SQLi, XSS, PHP, WordPress, Linux, and Account Takeover Prevention. For CMMC boundary controls, this provides baseline SI-3 (malicious code protection) and SC-5 (denial of service protection) out of the box. You can add custom rules, but the defaults cover the attack patterns that hit 90% of public-facing web applications.
State isolation is per module, per environment, per client. Each gets its own GCS state bucket and GCP project. Clean tenant separation for multi-tenant compliance boundaries. When an auditor asks "can Tenant A's deployment affect Tenant B's infrastructure?" the answer is no, architecturally, not just procedurally.
The GovCloud deployment itself is unremarkable by design. You point the modules at us-gov-east-1 or a GCP Assured Workloads project and the same YAML works. There is no separate GovCloud configuration format. The modules reference region and endpoint as configuration values, not hardcoded commercial endpoints. This is what makes cloud hopping possible: the same configs, different targets.
The Credential That Proved the Model
The project that taught me the most about what this architecture needs to do at scale wasn't a website.
In 2018, Blackboard partnered with Apple to put student IDs into Apple Wallet. We built the Blackboard side. Duke University was among the launch schools. Nine systems shipped: doors, dining, vending, transit, printing, laundry, rec centers, libraries, and campus events. The architecture needed to handle NFC transactions with sub-second latency across all nine, with zero tolerance for downtime because students relied on their phones to get into their dorm rooms.
A Transact executive later called it the most successful partner launch Apple had ever done of its kind. What made it work was not any single technical decision but the deployment model: declarative infrastructure, automated provisioning, and the ability to push updates across multiple cloud environments without downtime. The student credential system processed hundreds of millions of transactions across over a hundred universities and nearly two million students.
When Blackboard divested the unit in 2019 for $720 million, and when Roper Technologies acquired it five years later for $1.6 billion, the deployment pipeline was the operational backbone that made the business valuable enough to command those prices. (I wrote about what happened to Blackboard after losing this division, and why the pipeline was the difference between a business worth acquiring and one that filed for bankruptcy.)
That experience crystallized something: if you build the infrastructure layer right, the same operational model scales from a museum website to a mobile credential system processing millions of transactions. The question is never "can we build it?" It's "can we operate it at scale without the operational cost eating the margin?" (I explored this further in What the Operator Knows.)
How Government Actually Buys Things
Pricing infrastructure for government requires understanding procurement mechanics, not just cost structures.
The federal micro-purchase threshold is $15,000. Below this, a contracting officer can swipe a government purchase card. No competitive bidding. No solicitation period. No contracting officer review board. The Site tier at $7,500, a public-facing site deployed to the client's own GovCloud account, fits under that line deliberately.
The Simplified Acquisition Threshold is $350,000. Below this, procurement is dramatically streamlined: fewer compliance requirements, no full-and-open competition mandate, faster award cycles. The App tier at $25,000 and the HA App tier at $70,000 both clear this easily.
Then there's the SDVOSB advantage. As a Service-Disabled Veteran-Owned Small Business (I'm a USMC 2nd Reconnaissance Battalion veteran, OIF), UDX qualifies for set-aside contracts where we compete only against other veteran-owned firms instead of large integrators. Contracting officers can sole-source awards to SDVOSBs up to $5 million without competition. The VA must set aside contracts for SDVOSBs if two or more capable firms can compete. Federal agencies have statutory 5% SDVOSB spending goals that create demand-side pressure.
The combination is designed to be frictionless: SDVOSB status enables sole-source justification at high thresholds, while the pricing falls in simplified acquisition territory where contracting officers have maximum flexibility to move fast. A program office that needs a site in GovCloud can have it running without a procurement cycle.
These aren't hosting fees. The cloud account is in the client's name. The configuration files live in their repository. The pipeline runs in their GitHub organization. When the engagement ends, they own everything. There is no platform to renew, no environment to migrate off of.
The Stack Underneath
One thing worth going deeper on: the container runtime layer that actually serves these workloads.
The UDX Worker stack is a tree of purpose-built container images, each inheriting from a base that provides zero-trust secret resolution, process supervision, and a shared CLI for runtime inspection. The hierarchy matters:
udx-worker is the base: secrets, auth, supervisor, CLI. worker-php adds PHP-FPM 8.4 and Nginx. worker-site extends that with WordPress-specific wiring: Cloud SQL certificate injection, Git sync for theme and plugin code, Firebase state management. worker-nodejs provides a Node.js runtime for APIs and background workers. worker-engine adds a Firebase RTDB control plane for orchestration. worker-tooling is the CI/CD build agent image, and its child R2A is the Rabbit Automation Action container that runs the infrastructure pipeline itself.
Every image in the tree inherits the same secret resolution flow: a worker.yaml file defines what secrets the container needs, and at boot time, the Worker runtime pulls them from GCP Secret Manager and injects them as environment variables or files. Database passwords, SSL certificates, SSH keys, API tokens. None of them are baked into the image or stored in the orchestration layer. They exist only at runtime, resolved fresh on every container start.
This is what makes it possible to run 700+ projects on the same operational model. The images are versioned and tagged (worker-site:8.47.0). Upgrading the runtime across every client is a version bump in their k8s-deployment.yaml. Rolling back is the same. The blast radius of a bad runtime update is one client, one environment, one git revert.
What Fifteen Years Taught Me
I started UDX in 2011 in North Carolina. We've shipped over seven hundred projects. Our open-source tools have been downloaded 1.3 million times. We've worked with a Defense Prime. We helped launch the most successful Apple Wallet partner integration of its kind.
The through-line is the same observation I keep making: the gap between having cloud infrastructure and being able to use it is the defining problem of this era of software. Not AI. Not Kubernetes. Not serverless. The gap. The space between the primitives the cloud providers sell and the operational capability that organizations actually need.
Every year that gap gets more expensive to bridge with people. Every year the tools get better at bridging it with automation. Rabbit is our answer. Not the only one, but the one we built because we needed it for ourselves first and found that the organizations we work with needed it just as badly.
The website and the app are the same thing now. The infrastructure should be too.