PreviewOps
Turning pull requests into live preview environments
We designed and built PreviewOps, a developer infrastructure platform based on a simple idea: reviewing code should include reviewing the working software.
The result is a development workflow in which engineers, designers, product teams, and QA can experience a change before it becomes part of the main codebase.
- Service(s)
- Environment
- Preview DB
Why preview environments
The challenge
Modern development teams can produce code quickly, but meaningful review often still depends on a shared staging environment.
Automated tests and source review provide important evidence, but they do not always show how a change behaves as a running application. Reviewers may still need to answer:
- 01
Does the feature work end to end?
- 02
Does the frontend communicate correctly with the backend?
- 03
Does the application start with the intended configuration?
- 04
Does the interface behave correctly in a deployed environment?
- 05
Has the change affected another part of the application?
Shared staging environments create another constraint. Different branches compete for the same infrastructure, deployments overwrite one another, and teams must coordinate access to an environment that was meant to make testing easier.
Our goal was to move that feedback earlier without asking every developer to become an infrastructure operator.
A familiar workflow looks like this:
Traditional
- Code
- Pull request
- CI
- Review
- Merge
- Staging
- Test
PreviewOps
- Pull request
- Deploy preview
- Test
- Review
- Merge
Pull-request workflow
The product approach
A developer triggers a preview directly from the pull request with:
/deploy-previewopsFrom a pull-request comment, PreviewOps builds the application at the selected commit, deploys it to the configured cloud provider, checks that it is responding, and posts the resulting URL back to GitHub. Teams can use the default single-container workflow or configure multiple services, environment variables, performance checks, and an optional isolated Postgres branch for each preview.
PreviewOps then resolves the repository configuration, builds the Docker image for the requested commit, deploys it through the selected provider, waits for the service to become ready, and updates the pull request with its status and URL.
For the default single-container path, teams do not need to create a CI workflow or a PreviewOps configuration file. A Dockerfile and the installed PreviewOps GitHub App are enough, together with either stored cloud credentials or a managed PreviewOps plan. More complex applications can opt into repository configuration when they need multiple services, custom resource settings, environment variables, or provider-specific behavior.
This balance was important: the common path stays small, while advanced configuration remains available when the application requires it.
Preview lifecycle
Designing the preview lifecycle
- 01
A preview is requested
A developer runs the deploy command from an open pull request, or starts the same workflow from the PreviewOps dashboard. PreviewOps reads the pull-request metadata and targets the selected commit.
Teams can also configure alwaysOn branches. When one of those branches receives a new commit and has an open pull request, PreviewOps automatically redeploys it.
- 02
The application is built
PreviewOps builds a Docker image from the repository and associates the deployment with the commit under review. The build mechanism varies by provider: some use a managed cloud build service, while others build through the provider or a temporary compute resource.
- 03
Optional data infrastructure is prepared
Teams using the Preview DB add-on can receive an isolated Neon Postgres branch for the pull request. PreviewOps injects the connection string into the application and removes the branch when it is no longer referenced.
Database migrations remain application-owned and can run from the container entrypoint. Teams that do not use Preview DB can inject an existing database URL or run the preview without a database.
- 04
Services are deployed
The default workflow deploys a single container service. Monorepos can explicitly define multiple services, each with its own Dockerfile, build context, port, resource settings, and environment variables.
PreviewOps deploys those configured services through a common provider interface. Multi-service support and networking behavior depend on the selected cloud provider, so the platform exposes provider capabilities instead of pretending every cloud has identical semantics.
- 05
Readiness is verified
PreviewOps polls each deployed URL and marks the preview live only when all configured services respond successfully.
- 06
The preview evolves with the work
Developers can redeploy the preview as the pull request changes. For configured alwaysOn branches, pushes trigger that redeployment automatically.
- 07
Resources are removed
When the pull request closes—or the preview reaches its configured lifetime—PreviewOps removes the associated resources automatically.
When a deployed pull request is merged or closed, PreviewOps tears down its preview resources. A configurable time-to-live provides a second cleanup path, preventing abandoned environments from accumulating indefinitely.
The environment exists only while it remains useful.
Provider architecture
A multi-cloud deployment model
One of the central engineering decisions was to separate the product workflow from provider-specific infrastructure APIs.
PreviewOps uses a common deployment interface for building images, deploying services, checking active previews, and deleting resources. Provider adapters translate that interface into the native behavior of platforms including Google Cloud Run, Fly.io, Render, Railway, AWS, Azure, DigitalOcean, Hetzner, and Docker hosts reached over SSH.
This model gives teams a consistent pull-request workflow without hiding meaningful differences between providers. Capabilities such as automatic HTTPS, custom domains, and multi-service support are represented explicitly so teams can choose the deployment target that fits their application.
The URLs and HTTPS behavior are supplied by the selected provider. PreviewOps normalizes the workflow around them while preserving provider-specific capabilities.
- Google Cloud Run
- Fly.io
- Render
- Railway
- AWS
- Azure
- DigitalOcean
- Hetzner
- Docker over SSH
The abstraction is therefore not “every cloud works the same.” It is “the developer workflow remains consistent while the platform handles each cloud correctly.”
Application boundaries
Supporting full-stack applications
Frontend-only previews are comparatively simple. Full-stack applications introduce additional requirements:
- Multiple containerized services
- Runtime environment variables and secrets
- Database connectivity and isolated test data
- Provider-specific networking
- Health and readiness checks
- Build and startup failures
- Coordinated cleanup
PreviewOps-owned: deploy · inject · verify · remove
Application-owned: migrations · queues · caches · private networking
PreviewOps supports these requirements without claiming ownership of every external dependency. It can deploy explicitly configured application services, inject stored secrets, provision an optional Postgres branch, and verify the resulting service URLs. Application-specific concerns—such as migration commands, queue provisioning, cache topology, or private-network configuration—remain visible boundaries rather than hidden assumptions.
That boundary made the product more predictable. PreviewOps automates the repeated deployment lifecycle while allowing teams to retain control over application-specific infrastructure.
Operational truth
Making failures understandable
Deploying an application is not one operation. A preview can fail during validation, capacity checks, database provisioning, image building, service deployment, application startup, or readiness verification.
Returning only “deployment failed” would make the workflow difficult to trust.
- Validation
- Capacity
- Preview DB
- Build
- DeployActionable provider error
- Readiness
- Record
We modeled a deployment as a sequence of observable states. PreviewOps records progress through validation, capacity checking, optional database provisioning, build, deploy, readiness verification, and final recording. It stores build-log references and surfaces actionable failure messages in the pull request and dashboard.
Build logs, deployment progress, and failures remain visible through the pull-request experience and the dashboard.
The platform also retries selected transient operations and includes provider-specific recovery paths where they can be performed safely. Where automatic recovery is not appropriate, it gives the developer enough context to correct the application or configuration and redeploy.
The principle was simple: infrastructure complexity can be abstracted, but failure information cannot.
Resource discipline
Treating cost as a lifecycle problem
Ephemeral environments scale with development activity. Ten active pull requests can produce ten sets of resources.
We therefore treated cleanup and capacity controls as core architecture rather than later optimization. PreviewOps supports:
- PR #41×
- PR #42×
- PR #43×
- PR #44×
PR close · TTL expiry · concurrency control · reconciliation
- Automatic teardown when a deployed pull request closes
- Configurable preview lifetimes
- Per-repository concurrency limits
- Plan-level deployment and resource controls
- Provider-native scale-to-zero behavior where available
- Cleanup reconciliation for resources left by interrupted deployments
The objective is not to make temporary infrastructure free. It is to ensure that temporary work does not silently become permanent infrastructure.
Beyond deployment
Extending previews into a verification layer
A live environment is most useful when teams can validate it without assembling another testing workflow.
- 01AI-assisted pull-request analysis and coverage mapping
- 02HTTP smoke tests against a live preview
- 03Playwright test generation, review, approval, and execution
- 04k6 performance tests with latency, throughput, error rate, and SLO reporting
- 05Performance baselines and AI-assisted result interpretation
These capabilities use the preview URL as a shared execution target. Deployment, functional validation, and performance analysis remain connected to the same pull request and commit.
This turns the preview from a disposable demo into evidence that a proposed change behaves as expected.
Team impact
What the platform enables
PreviewOps gives teams a practical way to move application-level feedback earlier, before merge.
It enables:
- 01
Developers to test deployed integrations without competing for one staging environment
- 02
Designers to review a working interface through a shareable URL
- 03
Product teams to interact with work while it is still changeable
- 04
QA teams to test a feature in the environment associated with its pull request
- 05
Reviewers to combine source review and automated tests with evidence from the running application
These are product capabilities rather than claimed customer-performance metrics. The value comes from making a deployed application part of the normal review workflow.
Scope of work
Our contribution
Our work spanned the product and engineering layers of PreviewOps, including:
- Defining the product strategy and developer workflow
- Designing the pull-request environment lifecycle
- Building the GitHub App, webhook, and dashboard integrations
- Creating a provider abstraction across supported deployment targets
- Implementing container build and service deployment orchestration
- Supporting explicit multi-service and monorepo configurations
- Designing the optional preview-database lifecycle
- Implementing deployment-state tracking, build logs, and failure reporting
- Building automated teardown, TTL cleanup, and reconciliation
- Creating QA, Playwright, and performance-testing workflows
- Designing the control-plane interface and developer experience
The project required product strategy, developer tooling, distributed systems, cloud infrastructure, backend engineering, testing systems, and interface design to operate as one coherent product.
Lessons
What the platform made clear
- 01
The best developer tools remove repeated decisions
Developers do not want another infrastructure system to operate. They want a reliable way to run and review their application. The most useful abstraction was a small default workflow backed by optional configuration—not an attempt to hide every possible infrastructure decision.
- 02
Lifecycle design matters as much as provisioning
Creating a resource is only half of an ephemeral-environment product. The system also needs to know how to update it, observe it, retry safe operations, and remove it. The pull request provided the lifecycle primitive around which those decisions could be organized.
- 03
A good abstraction preserves operational truth
Making the common path simple does not justify concealing provider differences or deployment failures. The platform must explain what it is doing, where an operation stopped, and which concerns remain application-owned.
- 04
Developer experience shapes systems architecture
Requirements such as predictable deployment commands, shareable URLs, isolated data, readiness checks, and automatic cleanup appear to be interface decisions. In practice, they determine the event model, provider abstraction, persistence layer, and cleanup architecture behind the product. For developer infrastructure, product design and systems design are inseparable.
What comes next
Future direction
Preview environments are the foundation for a broader verification workflow.
As code generation becomes faster, the harder question is increasingly whether a generated change behaves correctly and is safe to ship.
The long-term direction for PreviewOps is a system in which automated tools can:
- 01Generate a change
- 02Deploy it independently
- 03Run tests and simulations
- 04Observe its behavior
- 05Compare results with expected outcomes
- 06Present evidence for a release decision
PreviewOps already provides the deployment and verification primitives behind that direction. Autonomous infrastructure generation, automated correction, and release decisions remain future capabilities—not claims about the current product.
Closing perspective
The product is being built around the layer between generated code and trusted software.