How I Built LabStack: Spinning Up Dev Labs in the Cloud from Scratch

Anirudh KamathAnirudh Kamath
9 min read

🚀 What is LabStack?

LabStack is a cloud-native platform that lets developers and students spin up disposable, browser-accessible labs in seconds — no local setup, no messy installs. Think VS Code or a Linux terminal in your browser, running in an isolated Docker container on the cloud, with auto-expiry built in.

🧠 What Problem Was I Solving?

I always found setting up development environments to be a huge time sink — especially for short-lived tasks like quick experiments, debugging, or teaching someone a tool. I wanted something simple like:

"Click a button → get a dev lab → it expires after 15 mins"

No messy installs, no local setup nightmares, no bloated platforms like full-blown Kubernetes or Codespaces.

I wanted it to be:

  • Fast

  • Lightweight

  • Secure

  • Built from scratch so I could deeply understand modern DevOps and cloud-native patterns (instead of using a black-box service)

And that's how LabStack came to life.

So far, I’ve built 4 fully working labs, each tailored for different needs:

  1. Python Lab → VS Code in the browser with Python preinstalled

  2. Networking Lab → Web terminal with classic Linux CLI tools (ping, traceroute, etc.)

  3. Python CLI Lab → A REPL-based terminal-only Python setup

  4. SQL Lab → A simple SQLite CLI in a browser terminal via gotty

And I can spin them up in under 30 seconds — all through a browser 🌐⚡


A Brief Background of the Project

This is my first cloud-based project and my first step into the world of Cloud and DevOps. The main goal was to build a cloud-native platform that's scalable but completely free—no surprise AWS bills!

Here's the challenge: AWS free tier gives you 750 hours of t2.micro EC2 instances per month. If I kept even one instance running 24/7, I'd use up 744 hours (24 × 31 days) and have almost no room left to experiment with other cloud services on the same account.

This constraint actually forced me to think creatively about architecture. Instead of always-on infrastructure, I built everything to spin up on-demand and auto-terminate. It's not just about staying within free tier—it mirrors real-world cloud optimization where you're constantly looking to minimize costs.

So while my setup might seem less scalable than a full ECS cluster, it taught me to build lean, efficient systems that only consume resources when actually needed.


🏗️ Project Architecture (At a Glance)

A brief outline of LabStack

Here's a high-level look at how LabStack works behind the scenes 👇

🌐 Frontend (Vercel)

This is where the user interacts — they choose a lab type, set how long they want it for (TTL), and hit "Launch Lab". It's built with Next.js, styled using Tailwind CSS and shadcn/ui, and hosted on Vercel for instant deployments.
I chose Vercel mainly because I did not want to worry about the frontend in this project. Also, its alright to choose conviniece once in a while, amazing tool i must say!

🛡️ API Gateway

Every frontend action (like launching a lab or checking status) goes through AWS API Gateway. Think of it as the entry gate to our backend — it routes the request to the right Lambda function securely over HTTPS.

⚙️ Lambda (FastAPI + Zappa)

This is where the real magic happens.

  • When a lab is requested, Lambda:

    • Picks a free port

    • Launches an EC2 instance using boto3

    • Boots the container with a user_data script

    • Probes the container to make sure it’s up

    • Updates the lab's status in DynamoDB

We used FastAPI here for blazing fast responses, and packaged it for Lambda using Zappa.

📦 DynamoDB

We use DynamoDB to track everything about the lab:

  • Status (PENDING, RUNNING, etc.)

  • TTL (when to auto-expire)

  • EC2 and Docker container metadata

  • Access URL for the user

💻 EC2 + Docker

Each lab runs inside a Docker container on its own EC2 instance. We use a custom AMI that already has Docker installed, and optionally pre-pulled lab images from GitHub Container Registry for faster startup. The GHCR images used are here: https://github.com/kamathanirudh?tab=packages

🐳 Docker Container

This is the actual lab environment — for example:

  • A code-server with Python

  • A Debian terminal for networking labs

  • A SQLite terminal via gotty or ttyd

Each container runs on a fixed internal port (like 8081) and exposes a web UI that opens right in the user’s browser. No login, no setup — just pure hands-on dev.


🔁 How a Lab is Born: Step-by-Step User Flow

Let’s walk through what actually happens under the hood when someone launches a lab on LabStack 🧪💻

1️⃣ User Clicks “Launch Lab”

The journey begins on the frontend (Vercel), where the user picks a lab template — like Python, Networking, or SQL — and chooses how long it should run (say, 15 minutes).

They hit Launch, and we send a POST /labs request to the backend with that info.

2️⃣ API Gateway → Lambda (create_lab)

That API call goes through AWS API Gateway, which routes it securely to a Lambda function that’s running our backend (written in FastAPI and deployed with Zappa).

This Lambda function handles lab creation logic.

3️⃣ DynamoDB Gets the First Word

The backend immediately generates a unique lab_id using Python’s uuid.uuid4().

It stores an entry in DynamoDB with:

  • status: PENDING

  • template_id: lab type (e.g. python_lab)

  • ttl_timestamp: auto-expiry time

  • and other metadata like created_at

This acts like a placeholder while we’re setting things up.

4️⃣ Booting the EC2 + Docker Container

Behind the scenes, the Lambda function launches an EC2 instance using boto3 with a pre-baked AMI (Amazon Machine Image) that already has Docker installed, is ruuning on Amazon Linux 2023, has pre-pulled images of the labs in it, is in a public VPC, Subnet, with the RT (Route Table) and IG (Internet Gateway) setup

It passes a user_data script to the instance — this is basically a bootstrap script that:

  • Runs the right Docker image (e.g. labstack-python-lab)

  • Runs the container with the right port exposed (like 8081)

5️⃣ Health Check Time

Once EC2 is up, Lambda doesn’t blindly assume it’s ready.

It:

  • Waits for the instance to reach running state

  • Waits for a public IP

  • Sleeps a few seconds

  • Then sends an HTTP GET to something like http://13.x.x.x:8081/labs

  • If the container replies — it’s alive!!

6️⃣ Update DynamoDB with Access Info

Once the lab is confirmed healthy, Lambda updates the DynamoDB entry:

  • status becomes RUNNING

  • access_url is set to the lab’s public IP + port (e.g. http://13.x.x.x:8081)

This makes the lab ready to use 🎯

7️⃣ Frontend Keeps Polling

While all this is going on, the frontend is quietly polling GET /labs/{lab_id}/status every few seconds.

As soon as it sees the lab’s status turn RUNNING, the user is redirected to the lab’s live environment in the browser.

8️⃣ Cleanup: TTL or Manual Termination

Labs don’t live forever (by design).

  • If the TTL expires, DynamoDB’s TTL mechanism automatically deletes the lab entry, and an optional cleanup Lambda can be triggered to shut down the EC2 instance.

  • Or, if the user clicks "Terminate", we send a POST /labs/{lab_id}/terminate to stop the container and destroy the EC2 manually.

It all feels instant to the user. But under the hood? There's an elegant orchestration of APIs, AWS services, and container magic ✨


⚙️ Port Management (Local vs Cloud)

  • Local MVP:

    • Use socket.bind() to find free host ports (8000–9000)

    • Map to container port (e.g., 8080)

  • AWS EC2:

    • Each instance uses a fixed container port (e.g., 8081)

    • No conflicts since one container per EC2


🧨 TTL-Based Cleanup

  • When a lab is created, a ttl_timestamp is set in the future

  • DynamoDB auto-deletes the item after expiry

  • A background Lambda (optional) checks and terminates EC2 instances with expired labs


🧠 Why I Didn’t Use ECS or ECR

“Why not just use AWS ECS + Fargate with ECR? Wouldn’t that handle container orchestration for you?”

Fair question! But here’s why I chose not to:

❌ ECS + Fargate = Not Free Enough

  • Fargate sounds magical (serverless containers!), but it’s not included in the AWS Free Tier in a usable way.

  • I wanted full control over the environment and cost — I didn’t want AWS to spin up surprise compute behind my back.

  • With Fargate, I’d also lose visibility into how exactly my containers were running, and debugging would’ve been harder.

❌ Running ECS on EC2 = Always On = Costly

  • If I used ECS with EC2 launch type, I’d have to keep a cluster of EC2 instances always running.

  • That defeats the point of on-demand labs — I didn’t want idle compute running 24/7 just waiting for labs to launch.

❌ ECR = Amazing option but Paid Storage

  • ECR (Elastic Container Registry) also isn’t free if you store multiple versions of large Docker images.

  • My Docker images were around 200mb each, and ECR free tier can help me store upto 500mb max. If cost wasn’t a constraint, this is an amazing option and id pick this!

  • Instead, I host all my lab images on GitHub Container Registry (GHCR), which:

    • Integrates well with CI/CD

    • Gives me unlimited private image storage for free (on personal GitHub accounts)

    • And works well with docker pull from EC2

So I stuck with what I needed:

  • EC2: Booted only when a lab is needed

  • GitHub Container Registry: Pulls fast and free

  • Lambda: Lightweight, serverless, cost-efficient

This gave me maximum control with minimal cost — perfect for a solo builder or student project on a tight budget 💸


🩹 Constraints I Faced

  • Lambda timeout limits (29s)
    → Solved via async pattern: POST returns immediately, polling updates status

  • Docker image startup delays
    → Solved with HTTP health checks to confirm readiness

  • Vercel frontend needs to know IP
    → Solved by returning full access_url from backend once lab is ready, and this is the main reason i used API Gateway

  • No user auth (yet)
    → Currently public, but IAM + Cognito can be added later


🎯 What’s Next?

  • Multi-container support (per EC2 or move to ECS)

  • User dashboard with auth (see your labs, usage)

  • Instructor-facing lab builder UI


✨ Final Thoughts

LabStack helped me learn more about:

  • Serverless patterns (Lambda + DynamoDB)

  • EC2 provisioning with boto3

  • Docker image optimization for fast boot

  • Building cloud-native tools with real-world constraints

It was tough. But it worked. And now I can launch a dev lab in under 30 seconds from anywhere 🌎💻

Would love your feedback!
Try it out (DM me for the link), star it on GitHub, or let me know how you’d improve it 🚀
Heres the GitHub Repo : https://github.com/kamathanirudh/labstack


10
Subscribe to my newsletter

Read articles from Anirudh Kamath directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anirudh Kamath
Anirudh Kamath

Hey! I’m a 20-year-old engineering undergrad passionate about exploring the vast world of tech. Currently diving deep into Cloud, DevOps, and AI/ML, building projects that bridge infrastructure with intelligent systems.