Profile
Back to NewsBack
Dev.to 9 min
Reader Mode
"Preview environments for people who are never going to run Kubernetes": Gautam's Zerops project

"Preview environments for people who are never going to run Kubernetes": Gautam's Zerops project

4 hours ago

Introduction

Over the weekend of August 8 and 9, Zerops ran The Zerops Challenge in collaboration with WeMakeDevs. Forty eight hours, solo entries only, and one hard rule: the finished thing had to be a real application deployed on Zerops with a live URL, still running when the judges reached it. Not a local demo, not a screen recording.

Over 1,000 developers signed up, more than 10,000 build and deployment pipelines fired, and 250+ valid projects came out the other end. Seven of them won. One was Ephemera, by Gautam Khosla, and it is still running.


First, introduce yourself.

I'm Gautam Khosla, a Computer Engineering student at the University of Ottawa.

Most of what I build sits in the same neighbourhood: systems that keep working when something underneath them stops. Edge AI running on QNX where the safety loop never touches the network. MicroVM snapshot work, measuring how fast a Firecracker guest gets back to usable. A transparency log for agent tools that quietly rewrite their own descriptions after you've already trusted them. The unifying assumption is that networks drop, APIs die, and hardware misbehaves, so I would rather plan for that on day one than discover it in the retro.

Outside the engineering side, I run a YouTube channel called Gautam Talks, where I film hackathons and builds as they actually happen, including the parts that go badly. The tagline is "start before you feel ready," which is roughly how I ended up doing this challenge solo over a weekend.

What did you build, and where did the idea come from?

Ephemera gives every pull request its own full stack.

Open a PR, and you get an API, its own Postgres, and a live URL posted straight back as a comment on the pull request. Close the PR and the whole environment disappears. There is a TTL reaper too, so anything you forget about cleans itself up before it turns into a bill you were not expecting.

The idea came from a gap that has bothered me for a while. Frontend teams have had preview environments for years. Vercel gives you a URL per pull request, and everyone moved on with their lives. Backend teams mostly have not, because every full-stack option I could find assumes you are already running Kubernetes and willing to sit through a sales call. So small teams and open source maintainers end up sharing one staging database, queueing behind each other, and occasionally breaking each other's migrations on a Friday afternoon.

Ephemera is preview environments for people who are never going to run Kubernetes. One preview.yml file, about ten minutes of setup, real infrastructure underneath. There is also a Docker Compose importer that drafts that file for you, because setup friction is the actual reason small teams do not already have this.

Walk us through the architecture.

The decision I care most about is that the webhook does almost nothing.

When GitHub fires, Ephemera verifies the HMAC signature, writes down what state the environment should be in, and returns in about 40 milliseconds. That is the entire handler. No provisioning, no calls out to Zerops, nothing that can hang or half-finish.

A separate worker then reconciles what should be true against what is true, taking exactly one idempotent step per tick:

pending → provisioning → deploying → ready → destroying → destroyed

Every step is resumable, and the worker takes an advisory lock per environment so two ticks can never fight over the same row.

That sounds like over-engineering for a weekend project right up until you actually try to provision infrastructure. It is slow, it fails halfway through, and it will happily tell you it worked when it did not. I spent part of the build deliberately killing the worker mid-provision, over and over, to see what happened. It always converged instead of leaving half-created environments rotting in the project. If I had written this as one long imperative provision() function, I would have lost the entire Sunday to orphaned services and inconsistent state.

This is the same idea as most of my other work, honestly. My site has a principle on it called "design for the outage first," and Ephemera is that principle pointed at infrastructure orchestration instead of a device on a factory floor. The failure modes are different. The assumption is identical.

The control plane itself runs on Zerops as five services:

Service Type Job
web Static React dashboard
api Node.js Webhook receiver, REST API
worker Node.js, no public port Reconciler and TTL reaper
db PostgreSQL Control plane state
queue Valkey Job queue and locks

Preview environments land in a second Zerops project, created with zcli project service-import, deployed with zcli push, and exposed on generated subdomains.

Two details I am glad I got right. Ready means ready. An environment is only marked ready once an HTTP request to its public URL actually succeeds. "The container exists" and "the app works" are very different claims, and I learned that the hard way when my dashboard cheerfully reported ready while the URL returned 502.

The provider is an adapter, not a dependency. It sits behind a four-method interface: create, deploy, status, destroy. Zerops is the first implementation rather than something the project is permanently welded to.

Measured end to end: pull request opened to live URL in roughly 100 seconds.

What did Zerops get wrong most often, and how did you catch it?

I wrote all of this up properly in the gist, but there is one pattern behind almost every issue I hit: silent failures. Not crashes, not error messages. Things that accept your input, report success, and then quietly ignore you.

The worst one cost me two hours. PostgreSQL will not create with a 1 GB disk. It wants 5. But instead of a validation error saying so, service import returns a generic Internal Server Error. So I went looking for account limits, leftover state from deleted services, a platform incident, anything except the disk. I found it by bisecting my generated import YAML field by field against a hand-written one that worked, which is a memorable way to spend the small hours.

The second was that run.start is exec'd rather than run through a shell. So "migrate && start" runs the migration, exits happily, and never starts your server. The container then crash-loops against the health check while you debug an application that is completely fine. The tell is right there in the runtime log, which prints the word "exec" immediately before it happens. I just did not know yet that it mattered.

Third, setting enableSubdomainAccess: true in the import YAML. Accepted, service created, import reports success, toggle stays off, URL returns 502. Of the three available behaviours, honour it, reject it, or ignore it, ignoring is the unkind one, because it looks exactly like success.

The hardest to find was that operations serialise per service. With one environment at a time, everything worked flawlessly all night. With two provisioning concurrently, my enable-subdomain call sat behind a running deploy, timed out, and my retry logic queued another one behind the first. Queue depth went up, nothing completed, and every environment eventually failed on a 502 that was really just a subdomain nobody had turned on. The behaviour itself is completely reasonable. The problem is that it is invisible from the client, and the obvious implementation makes it worse.

How did I catch them? The logs, genuinely. Almost every diagnosis in that gist started with reading a pipeline or runtime log and finding exactly the line I needed sitting right there. The build logs are unusually good. The gap is between what the logs show you and what the API tells you, because the API is where things go quiet.

What did you not understand about Zerops when you first opened it?

Three things, one of them pleasant.

First, that zerops.yml and the import YAML are two different files doing two different jobs. One describes how to build and run a service, the other creates the infrastructure. Completely obvious once you have used it, genuinely confusing when everything in front of you is called "the YAML."

Second, the service state model. Runtime services sit at READY_TO_DEPLOY until code is pushed, while databases go ACTIVE immediately. My first status polling loop waited for everything to be ACTIVE before deploying, which meant it sat there waiting forever for the deploy that could only happen after the deploy. A documented list of the possible service states would have saved me an evening.

Third, and this one in a good way: I did not understand that the private network would simply work. I had budgeted real time for networking. Generated cross-service environment variables meant I wrote precisely zero networking code. The control plane and the preview environments just found each other. After a while of writing infrastructure YAML, that still feels slightly magical.

Was this your first hackathon?

Not even close, I did four in a month at one point. I have an MLH award for Best Use of Snowflake API from HackConcordia, and a top-9 finish at McHacks Pre-Hackathon challenge.

What was different this time is that I usually build something demo-shaped, present it, and move on. This one I actually want people to use. It is open source, it is still running, and the work I deliberately deferred over the weekend is on a list rather than in a drawer: encrypting tokens at rest, making the reconciler tests run without Docker, and deciding whether the better wedge is PR previews for small teams or one-click live demos for devtool companies.

The other difference is that this was solo, and about twenty of the hours went into the reconciler rather than the UI. In a team hackathon, somebody would reasonably have argued for the reverse. Doing it alone meant I got to be wrong on my own terms, which suited the project.


Ephemera

The Zerops Challenge

Connect with Gautam Khosla

Check out our other articles to find out more about our winners.

Chat with me