Skip to Content
← All Work

Case study · Prepare.sh · 2025–2026

Rebuilding the developer environment stack

Interactive developer environments exposed the limitations of the existing Azure AKS and vCluster approach. I contributed to the move to bare-metal Hetzner infrastructure running K3s, alongside a new GitHub Actions-based delivery workflow.

At a glance

Isolated environment provisioning
2–3 min → <1 min
Monthly infrastructure cost, as reported by the company after the migration
~$7,900 → ~$330
Users on the platform
~10,000

Role · Founding Engineer, Cloud & DevOps

What I did

  • Worked alongside the founder across the product: web development, practice content and labs, client conversations and deployment.
  • Evaluated Docker-in-Docker limits on Azure AKS / vCluster.
  • Contributed to the migration to bare-metal Hetzner K3s with GitHub Actions CI/CD.
  • Contributed to the move of CDN, object storage and databases from Azure to Cloudflare R2, D1 and Workers.
  • Built reusable Terraform modules and Helm deployments.

Team context

What the team did

  • The learning platform, its content and the product itself were built by the wider team.
  • The company’s cost figures reflect the migration as a whole, not my individual work.
  • Later scaling of the platform was handled by others. I was not directly involved in it.

01

Context

Prepare.sh is a developer-education platform: learners work inside interactive, hands-on technical environments rather than reading about them. Roughly 10,000 users were served.

The first infrastructure ran on Azure: AKS for compute, Azure DevOps YAML pipelines for CI/CD, Azure Container Registry, Functions, Event Hub, Blob Storage and Azure CDN for media.

02

Problem

Each learner needs an isolated environment that behaves like a real machine, including the ability to run containers inside it. Docker-in-Docker on AKS with vCluster isolation ran into limitations, and an environment took about 2–3 minutes to provision, which is slow for an interactive experience.

03

Constraints

  • Environments had to be isolated from one another.
  • Learners needed to run containers inside their environment.
  • Provisioning latency shapes an interactive experience.
  • A small engineering team, so the stack had to be operable without a dedicated platform group.
  • Infrastructure spend: the platform was moving off hyperscaler cloud credits.

04

Architecture

Developer environment architectureGitHub Actions deploys to a bare-metal Hetzner K3s cluster that runs isolated learner environments. Static assets, object storage and databases sit on Cloudflare R2, D1 and Workers. A dashed box shows the earlier Azure AKS and vCluster setup.AFTERGitHub ActionsCI/CDK3s · Hetzner bare metalself-managed clusterenvenvenvisolated · provision <1 minCloudflareR2object storageD1databasesWorkers+ CDNBEFOREAzure AKS · vClusterDocker-in-Docker limits · provisioning ~2–3 min · ACR, Functions, Event Hub, Blob, CDN
Fig. 01: Architecture

Target architecture after the migration.

  • GitHub Actions builds and ships to a bare-metal K3s cluster on Hetzner.
  • Each learner request creates an isolated environment on that cluster.
  • Static assets, object storage and databases sit on Cloudflare R2, D1 and Workers.
Service migration mapAzure AKS and vCluster moved to K3s on Hetzner. Azure DevOps pipelines moved to GitHub Actions. Azure CDN, object storage and databases moved to Cloudflare CDN with Workers, R2 and D1.BEFORE · AZUREAFTER · SELF-MANAGED + CLOUDFLARECOMPUTEAzure AKS + vClusterCOMPUTEK3s on Hetzner bare metalCI/CDAzure DevOps YAML pipelinesCI/CDGitHub ActionsCDNAzure CDNCDNCloudflare CDN + WorkersOBJECT STORAGEAzure Blob StorageOBJECT STORAGECloudflare R2DATABASESAzure-hosted databasesDATABASESCloudflare D1
Fig. 02: Service migration map, Azure to Hetzner and Cloudflare

05

Alternatives

What happened

Azure AKS with vCluster isolation was the starting point, and was evaluated against Docker-in-Docker requirements before the move to bare metal.

Retrospective analysis

Stay on AKS with a different isolation model
Plausible, but retained the cost profile and the managed-cloud constraints that were already pushing the team toward a different stack.
Managed Kubernetes elsewhere
Would change the vendor, not the underlying constraint between container isolation and nested containers.
Per-learner VMs
Strong isolation, but typically slower to start and heavier per environment than containers.

06

Decision

Move to bare-metal Hetzner running K3s, with GitHub Actions for CI/CD. K3s is a lightweight Kubernetes distribution. The documented drivers were the Docker-in-Docker limitations on AKS/vCluster and the wish to move off hyperscaler cloud credits and cut recurring spend.

Alongside that move, CDN, object storage and databases went from Azure to Cloudflare R2, D1 and Workers.

07

Implementation

  1. 01

    Investigate

    Evaluated Docker-in-Docker behavior on AKS and vCluster to understand where the limitations came from before choosing a replacement.

  2. 02

    Cluster

    Stood up the bare-metal Hetzner K3s cluster that isolated environments run on.

  3. 03

    Delivery

    Replaced Azure DevOps YAML pipelines with GitHub Actions CI/CD for the new stack.

  4. 04

    Platform services

    Moved CDN, object storage and databases to Cloudflare R2, D1 and Workers, removing the dependency on Azure services.

  5. 05

    Repeatability

    Wrote reusable Terraform modules and Helm deployments so environments and services could be provisioned without manual configuration.

08

Results

  • Isolated developer environment provisioning dropped from approximately 2–3 minutes to under 1 minute.
  • Company-reported: infrastructure cost fell from roughly $7,900 per month on Azure to roughly $330 per month after the migration. The figure comes from the company’s founder and reflects the migration as a whole, not my work alone.
  • Reusable Terraform modules and Helm deployments replaced manual configuration for environment setup.

Measurable outcomes

2–3 min → <1 min
Isolated environment provisioning
From my work
> 50% faster
Provisioning time reduction, comparing the fast end of the old range (2 min) with the new ceiling (1 min)
Calculated
> 1 min saved
Saved per environment created, using the same comparison
Calculated
~$7,900 → ~$330 / month
Reported infrastructure cost, before and after the migration
Company-reported
≈96% lower
Monthly cost reduction: ($7,900 − $330) ÷ $7,900
Calculated
~$90,800 / year
Annualized difference ($7,570 × 12), assuming the monthly figures hold
Calculated
GitHub Actions
CI/CD for the new stack, replacing Azure DevOps pipelines
From my work
Terraform + Helm
Reusable modules replaced manual configuration for environment setup
From my work
Provisioning time before and afterBefore, provisioning an isolated environment took about two to three minutes on Azure AKS. After the move to bare-metal K3s it takes under one minute.01 min2 min3 minAzure AKSbefore~2–3 minBare-metal K3safter< 1 min
Fig. 03: Environment provisioning time, before and after (approximate)
Monthly infrastructure cost before and afterThe company reported infrastructure costs of about 7,900 dollars per month on Azure, and about 330 dollars per month after the migration, roughly 96 percent lower.$0$4,000$8,000Azurebefore~$7,900 / monthAfter migrationafter~$330 / month≈96% lower (calculated)
Fig. 04: Monthly infrastructure cost before and after the migration. Company-reported; reflects the migration as a whole.

09

Trade-offs & Failure Modes

From the project

  • Moving to bare metal and a self-managed stack means the team now operates the cluster itself, in exchange for control over isolation, latency and cost.

General analysis

Node failure
On bare metal there is no managed control plane to absorb hardware faults; recovery depends on spare capacity and how quickly workloads can reschedule.
Noisy neighbors
Many isolated environments sharing nodes need explicit CPU, memory and storage limits to stay fair.
Security boundary
Nested containers need careful privilege and runtime choices; isolation is only as strong as those choices.
Upgrades
A self-managed K3s fleet makes version upgrades and certificate rotation the team's job.

10

Lessons

  • Measure the thing the user feels. Provisioning time was the metric that justified the move.
  • Understand the constraint before replacing the platform: evaluating the Docker-in-Docker limits first made the migration a decision rather than a reaction.
  • Match the architecture to the actual workload. Interactive, short-lived, isolated environments have different needs from a typical web service.

11

Questions People Ask

The follow-ups this work tends to raise, answered directly.

What was wrong with Docker-in-Docker on AKS and vCluster?

Interactive environments need to run containers of their own. On AKS with vCluster isolation, that ran into limitations, which I evaluated before choosing a replacement. Those limits, plus the push to move off hyperscaler credits, led to the move to bare-metal K3s.

Why is nested Docker hard on managed Kubernetes in general?

Running containers inside a container typically needs elevated privileges or special runtimes, and managed Kubernetes and virtual clusters tend to restrict both. Bare metal gives full control over those choices.

General context

How much faster did environments get?

Provisioning an isolated environment went from approximately 2–3 minutes to under 1 minute.

What did the migration cost and save?

The company’s founder reported infrastructure costs of roughly $7,900 per month on Azure versus roughly $330 per month after the migration. That is the company’s result for the migration as a whole, not something to attribute to me alone.

Why K3s?

K3s is a lightweight Kubernetes distribution, which suits a small team running its own bare-metal cluster.

General context

Did you build all of this yourself?

I evaluated the Docker-in-Docker limits, contributed to the migration to bare-metal K3s and built the Terraform and Helm modules. The product and application code were built by the wider team, and later scaling of the platform was handled by others.