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

Table of contents

🚀 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:
Python Lab → VS Code in the browser with Python preinstalled
Networking Lab → Web terminal with classic Linux CLI tools (ping, traceroute, etc.)
Python CLI Lab → A REPL-based terminal-only Python setup
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)
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
scriptProbes 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 timeand 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
stateWaits for a public IP
Sleeps a few seconds
Then sends an HTTP
GET
to something likehttp://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
becomesRUNNING
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 futureDynamoDB 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 statusDocker image startup delays
→ Solved with HTTP health checks to confirm readinessVercel frontend needs to know IP
→ Solved by returning fullaccess_url
from backend once lab is ready, and this is the main reason i used API GatewayNo 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
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.