R7K1

Disposable branch previews with isolated execution, atomic routing, and content-addressed reuse of identical build work.

Personal project

Project brief

Period
2016
Status
Archived
Focus
Isolated preview infrastructure
Constraint
Unaccepted code needed isolated execution, bounded resources, and expiry.
Result
Immutable build identity enabled safe previews and reusable work.

R7K1 was a 2016 personal project that turned branches from diffs into temporary, inspectable applications. M31V had given one proposed publication a faithful preview and one immutable artifact. R7K1 raised the difficulty: several repositories could change concurrently, each branch could execute unaccepted code, builds could be expensive, and every environment needed a stable URL, isolated resources, an expiry path, and enough evidence to explain failure.

Decision record

What made a preview trustworthy?

Decision
Identify it by commit, build inputs, isolated data mode, route, and expiry—not merely a branch name.
Trade-off
The controller carried more lifecycle state, but reviewers stopped seeing partial or ambiguous environments.

One reviewed artifact became many disposable environments

The motivation was personal and practical. I was sharing small web experiments with a circle of reviewers. Asking someone to check out a branch, install the right runtime, reproduce configuration, and seed data made feedback depend on development setup. Screenshots helped with appearance and hid keyboard flow, responsive behavior, loading states, and integration defects.

Docker had made it plausible to package each application, but “run this container” was far from a trustworthy preview service. The first shell script proved the distance. It built an image, started a container, added a proxy rule, and printed a URL. When a middle command failed, it left containers, routes, and directories behind. When a branch updated quickly, it built several previews nobody would ever open.

R7K1 became an exercise in lifecycle design. The URL was only the visible end of a controller that had to know what existed, what had been reviewed, what could be reused, and how every partial state would be removed.

The preview contract

I wrote a contract before adding another worker:

  • A preview URL identified a repository, branch, commit, build artifact, data mode, and expiry.
  • Updating a branch never exposed a partially built replacement.
  • Untrusted preview code received no production credentials or production data.
  • Resource, network, and lifetime limits applied before code ran.
  • Repeating the same declared build work could reuse a validated artifact.
  • Failed, superseded, and expired previews left no permanent route or compute resource.
  • A reviewer could understand build status and failure without learning the controller’s internals.

The contract avoided a stronger claim: R7K1 was not a general-purpose hostile-code sandbox. It reduced risk for unaccepted code from known personal repositories on dedicated preview hosts. That boundary shaped every security decision and remained visible in the case study.

Replacing a script with a state machine

A repository webhook created a durable preview request. The controller stored each transition before scheduling the next action:

requested → queued → building → validating → ready → expiring → removed
                         ↘ failed
                  ↘ superseded

Workers claimed queued requests with expiring leases. A worker could renew its lease while making progress. If it stopped, another worker recovered the request only after the lease expired and inspected any recorded artifact state.

Every transition was idempotent. Cleanup tolerated an already removed container, absent proxy route, or missing workspace. Activation recognized when the requested artifact was already current. A repeated webhook could join the existing request instead of creating parallel work.

The controller separated branch identity from build identity. A branch was a moving collaboration reference. A commit named source. An artifact named the concrete result of declared build inputs. The branch URL could move from one validated artifact to another while each artifact stayed immutable.

This separation prevented an old, slow build from replacing a newer commit. Before activation, the controller checked that the branch still requested that commit. A superseded artifact could remain reusable without becoming visible at the branch URL.

M31V activated a static release by changing a symlink. R7K1 needed to activate a running candidate behind a stable route.

The controller started the candidate under a generated internal identifier. It waited for the container-level readiness check, then ran application-specific route tests against the internal address. Only a passing candidate entered the proxy’s route table.

The route update was atomic from the reviewer’s perspective. The previous preview stayed active until the new mapping was ready. Existing connections could drain for a short bounded period while new requests reached the candidate.

The earliest health check only verified that a process listened on a port. One preview passed while its application had failed to load required seed data and returned errors on every useful route. The final validation separated:

  • Process readiness: can the runtime accept a connection?
  • Application readiness: do the declared review routes behave as expected?
  • Preview readiness: are route, metadata banner, data mode, and external stubs all coherent?

A project supplied a small preview manifest listing its health path and a few critical checks. R7K1 constrained the format and execution time so a repository could not turn validation into an unbounded job.

Build identity and content-addressed reuse

Adoption of my own preview workflow quickly filled the single worker with repeated dependency installations and identical builds. Docker’s local layer cache helped when its history happened to align and did not give the controller a dependable answer to “Have these inputs already produced a validated artifact?”

R7K1 created a normalized build manifest containing:

  • Repository and source commit.
  • Dependency lockfile digest.
  • Build recipe version.
  • Base image digest rather than a mutable tag.
  • Declared non-secret build configuration.
  • Required generated assets and runtime shape.

The normalized manifest produced a digest. Requests with the same digest joined one in-progress build or reused one completed, validated artifact. Failed builds never entered the reusable set. Changing the build recipe or base image invalidated the identity even when source stayed the same.

This was the project’s key separation. Branch names answered what a reviewer wanted to see. Manifest digests answered whether the requested work was actually identical. Conflating those identities caused either waste or unsafe reuse.

The system did not claim bit-for-bit reproducibility. A package registry or compiler could still introduce variation despite declared inputs. R7K1 recorded the resulting artifact digest and linked it to the manifest. Reuse meant “serve the exact validated artifact produced for these declared inputs,” not “assume any rebuild will be identical.”

Coordinating concurrent requests

Two webhooks for the same manifest could arrive before either build completed. The first implementation checked the cache and started two workers during that race.

I added a durable build record keyed by manifest digest. A request could create it or join it. One lease-holder produced the artifact; other preview requests waited on the recorded outcome. When the build became reusable, every still-relevant request proceeded to environment creation.

If the lease expired, a new worker inspected whether an artifact upload had completed and verified its digest before deciding to resume or rebuild. Temporary workspaces never counted as artifacts. A half-uploaded archive used a temporary name and became visible only after digest verification and atomic rename.

Supersession applied at two levels. A newer push could mark an older branch request as no longer activatable. The underlying build could continue if another request depended on it or if it was close enough to completion to be useful in the content-addressed store. Otherwise the worker received a cancellation signal and cleaned up.

This avoided an over-simple policy where every new push killed useful shared work, and an equally wasteful policy where every obsolete build ran to completion.

Preview code ran on hosts separated from personal production services. Containers received CPU, memory, process, and duration limits. Build and runtime networks were distinct. Production credentials were absent by construction, not merely hidden from the interface.

Outbound access used an allowlist for package sources and a small set of stubbed integrations. Preview applications could not make arbitrary requests to the local network. Synthetic data was generated per environment and carried obvious test markers.

Secrets required for package download were mounted only during the relevant build step, excluded from the manifest, and unavailable in resulting image layers. Logs redacted known patterns, though the stronger defense was minimizing which secrets entered the environment at all.

User-controlled repository and branch names never became shell fragments, filesystem paths, container names, or DNS labels. The controller generated opaque identifiers and stored display names as data. Webhook repository identity and commit membership were checked before work entered the queue.

The preview banner identified the environment as synthetic and showed commit, build time, expiry, and data mode. It did not create a backdoor around the application’s authentication model; projects either supplied a preview-specific test login or ran with a deliberately public synthetic mode.

The reviewer-facing strip

The preview URL opened the application with a slim R7K1 strip above it. The strip carried only the context needed to interpret the environment:

  • Repository, branch, and abbreviated commit.
  • Ready, rebuilding, failed, or expiring state.
  • Artifact build time and declared recipe version.
  • Synthetic data notice.
  • Expiry and a controlled extension action.
  • Links to build evidence and the exact change.

The strip could collapse without disappearing completely. I wanted screenshots and responsive checks to be possible, but not at the cost of a preview being mistaken for an ordinary environment.

Build failures received a stable page at the same branch URL. It summarized which phase failed, showed a bounded relevant log excerpt, linked the full retained log, and named whether an older successful preview remained active. A failed replacement did not turn a working review URL into a generic proxy error.

Reviewers could compare two preview URLs side by side. R7K1 did not attempt visual diffing at first; it made both states accessible with enough identity to discuss them accurately.

Expiry was a product feature

Disposable environments become permanent accidentally unless time is part of their model. Each preview had an expiry derived from branch activity and request state. Closed or deleted changes expired quickly. Active branches received a visible deadline that a reviewer could extend within a maximum.

Expiry entered a warning state before removal. The branch page could explain that its environment had been reclaimed and offer a rebuild if the commit still existed. Links did not simply decay into an unexplained not-found response.

Cleanup used the same durable state machine as creation. It removed the proxy route, stopped runtime resources, released synthetic data, marked references to the artifact, and finally removed controller state according to retention policy. A failure at any step left a resumable transition.

Artifacts and environments had different lifetimes. An expired preview could release its container while a validated artifact remained reusable. The store tracked references and an age policy; deletion rechecked that no active request depended on the artifact.

I tested cleanup by interrupting it after each step. The shell-script prototype had treated cleanup as best effort. R7K1 treated it as normal system behavior with its own evidence and retry path.

One fast repository could otherwise monopolize the worker with rapid pushes. The queue grouped requests by repository and limited concurrent work per project. A small fairness rule selected among repositories before selecting the next request within one repository.

Pushes received a short debounce window. If several commits arrived while a person was editing, only the latest branch request normally proceeded to activation. A manual “build this commit” action bypassed debounce for a specific review need.

The interface reported position as a rough state rather than a precise countdown. Build duration varied too much for an exact promise. It showed queued behind active work, building, or waiting on a shared artifact.

Capacity remained intentionally modest. Instead of hiding saturation by launching unbounded hosts, the system exposed queue age and rejected new manual rebuilds when the daily resource budget was exhausted. A personal platform could still practice explicit capacity management.

These controls were the first time one of my projects had to allocate a shared resource among independent requests. The problem connected product behavior—what should a reviewer see?—to scheduling and cancellation semantics.

Evidence and debugging

Every request accumulated a structured timeline: webhook accepted, manifest computed, build joined or started, artifact verified, environment created, checks run, route activated, and cleanup completed.

Raw logs remained available, but the default failure view was organized around those transitions. A build that never acquired a worker differed from one whose compiler failed. A healthy container with a failed review route differed from a proxy activation error.

The controller recorded the exact image and artifact digests used by a ready preview. When a bug appeared, I could reproduce the reviewed artifact without rebuilding from a moving base tag.

A synthetic canary repository exercised the full lifecycle on a schedule. It built a tiny application, validated the route, updated it, confirmed atomic replacement, and expired it. The canary found broken proxy cleanup and certificate-routing issues that unit tests around the controller had missed.

The evidence model later informed W93H and N17Q. A complex run becomes reviewable when its meaningful transitions are first-class, and raw logs stay linked rather than being mistaken for the narrative.

Routes, names, and certificates

The earliest preview URLs used path prefixes under one host. Applications with absolute asset paths, root-relative redirects, or assumptions about cookie scope behaved differently there than they did at a host root. Rewriting every response would have made R7K1 responsible for application semantics.

I moved each preview to a generated subdomain under a dedicated preview origin. A wildcard certificate and constrained DNS pattern kept provisioning independent of branch names. The proxy map connected the generated route identifier to one validated environment; the reviewer-facing strip supplied the human-readable repository and branch.

The dedicated origin also limited cookie confusion. Preview applications could not share the primary site’s host-only cookies, and test authentication used preview-specific credentials. Response headers identified the environment and prevented indexing. The synthetic-data notice appeared in both the R7K1 strip and a machine-readable environment variable that projects could display in their own shell.

Routing state was versioned and validated before reload. The proxy never consumed a partially written configuration. If activation failed, the prior map stayed current and the request timeline retained the rejected candidate. A scheduled reconciliation compared controller-ready environments with proxy routes and reported orphaned entries in either direction.

Certificate expiry and DNS resolution joined the lifecycle canary. A container could be healthy while the public preview route was unusable. Testing only the internal address would have repeated the same mistake as testing only whether a port listened.

This work showed how seemingly administrative concerns become product behavior. The stable URL was the reason R7K1 existed; DNS, TLS, cookie scope, and proxy activation were therefore part of its user experience, not a layer beneath it.

At first, every preview ran one seed script after startup. The script drew random names and dates, so two reviewers looking at the same commit could see different edge cases. Some previews accidentally appeared empty because the random generator produced no records for a narrow category.

I changed seed data into a named, versioned fixture set. A project could declare minimal, ordinary, or edge-cases, each tied to a fixture revision included in the preview metadata. Deterministic pseudorandom values came from the environment identifier where variety was useful.

Fixture preparation ran before the application readiness checks. Its output was isolated per environment and removed during expiry. A schema migration in the candidate code applied to a fresh copy of the declared fixture, not a database shared with another branch.

Reviewers could switch fixture profiles only by requesting a new environment variant. The URL and strip then reflected the different data mode. Mutating one ready preview invisibly would have broken the connection between observed behavior and build identity.

The edge-case fixture deliberately included long text, missing optional images, empty collections, Unicode names, permission differences, and records near validation boundaries. This made preview links useful for more than the main happy path and connected J05N’s later component contracts to realistic product states.

Synthetic data could never reproduce every integration or scale property. Its contribution was repeatability. A review comment could name the commit, artifact, and fixture profile, giving the observed state enough identity to be found again.

Preview failures I kept as fixtures

The shell-script prototype was the first mistake. It encoded an ideal sequence and had no representation for partial progress. Durable states and idempotent transitions replaced it.

Immediate rebuild on every push filled the queue with previews no one would see. Debounce, supersession, and shared-build coordination corrected the product behavior before I considered adding workers.

The original health check proved only that a port was open. Application and preview-specific checks moved before activation.

An early cache key used source commit and lockfile only. A base-image update produced different output under the same identity. The normalized manifest grew to include every declared build input I could control, while the resulting artifact retained its own digest.

I also allowed branch names to influence hostnames directly. Even with character filtering, that mixed display input with infrastructure identity and created collision edge cases. Generated identifiers made routing dull and safe.

Finally, the first expiry job removed environments in one pass and ignored errors. Months later, stale routes exposed how incomplete cleanup accumulates. Expiry became an explicit, monitored lifecycle.

Disposable environments changed review

R7K1 moved my personal projects from deploying one application to operating a small platform for many temporary applications. It combined a durable controller, concurrent work coordination, content-addressed identity, application-aware validation, isolated execution, routing, reviewer experience, capacity, and cleanup.

The key growth was not the number of technologies. It was learning to separate identities and lifecycles that looked similar at first:

  • Branch, commit, manifest, artifact, environment, and route.
  • Build readiness, application readiness, and review readiness.
  • Environment expiry and artifact retention.
  • Superseded request and reusable work.

M31V’s immutable release concept survived, but it became one component in a system that scheduled, reused, and safely discarded artifacts. X8B6’s idempotent recovery reappeared in controller transitions. T04P’s truthful states reappeared in preview status.

The next project, J05N, shifted away from infrastructure while keeping R7K1’s hardest organizational lesson: creating a technically better thing does not make existing applications adopt it. J05N would explore shared interface contracts, accessibility, versioning, and migration across my own growing collection of projects.

The edge of the laboratory

R7K1 was not a secure multi-tenant cloud and did not promise containment against a determined hostile repository. It ran known personal projects on isolated preview hosts with reduced credentials, synthetic data, resource limits, and restricted networks.

Preview scale could not reproduce every production behavior. External integrations were often stubbed. Content-addressed reuse depended on the completeness of the declared manifest. The controller made those boundaries inspectable; it did not make disposable environments equivalent to every environment they helped review.

Technology: Docker, Git webhooks, durable controller states, leased build workers, content-addressed manifests and artifacts, reverse-proxy routing, synthetic data, restricted preview networks, resource limits, and lifecycle canaries.