Simplifying preview environments for everyone
Tags: docker, containers, opensource, devex
Published: 2023-06-28 11:34:52

Every open source project has a story. 

There’s almost always an interesting motivation behind it, a vision and roadmap in front of it, and a unique perspective of the maintainers on one corner of the industry.

So I’d like to take the opportunity to tell you the story of our open source project “[Preevy](https://github.com/livecycle/preevy)” - an open source tool that’s designed to make preview environments more accessible and affordable for dev teams everywhere.

In doing so, I’ll also share our unique perspective on the past, present and future of “preview environments” (also known as “ephemeral environments”, “review apps” or “deploy previews”) and how they can impact how teams work together to build and deploy products. 

## **My first encounter with preview environments**

To understand how we came to develop Preevy, let’s go back a few years to my first experience with “preview environments”, as I was exploring different ways to shorten our deployment flows.

We needed to conduct these manual steps:

* Merge a feature branch to the main branch after tests and code reviews
* Deploy the new image to a shared canary or staging cluster
* Run additional automatons and manual reviews
* Roll out the new image to production

Using that flow, it was quite common to encounter roadblocks: staging was often blocked, it wasn’t synced with the current state of the main branch, and we had to wait a long time before deploying to staging or production. Furthermore, we had to open and close multiple PRs to deploy a single feature due to fixes or bugs found between each of these steps, even when we leveraged our [powerful feature flag solution](https://github.com/soluto/tweek).

I attempted to eliminate this friction by taking a so-called “GitOps” approach and introducing ephemeral environments into our workflow.

The rules were simple:

![Preview environments with GitOps](https://livecycle.io/static/409272a2c77693ef4565d32cdacee2ae/17a7a/preview-environment-flow.png)

* Every commit to the main branch triggered a production deployment (with automatic [canary rollout](https://argo-rollouts.readthedocs.io/en/stable/features/canary/#canary-deployment-strategy)).
* Every commit to a feature branch triggered deployment of a new accessible [ephemeral environment](https://livecycle.io/blogs/our-take-on-ephemeral-environments).

While this solution significantly sped up our deployment process and eliminated manual steps, I was surprised to find that these environments were not only used by developers, they were also widely used by product managers, designers, and even external teams, mostly for trying out features and providing feedback in the early stages of the development process.

The collaborative and inclusive nature of these preview environments helped us to better meet product expectations, communicate efficiently on the state of the feature, improve our velocity, and ultimately, reduce our time to market while also improving our product quality.

### Enabling cross functional collaboration

Let’s fast-forward a few years. Today, preview environments [are](https://www.youtube.com/watch?v=IhPga1VukmI) [more](https://blog.tilt.dev/2022/02/17/preview-environments.html) [common](https://hackernoon.com/deploy-like-vercel-and-netlify-with-cloud-run-live-preview-and-modern-workflow), and I’ve co-founded a startup called [Livecycle](https://livecycle.io/) with the mission to make development flows more collaborative and [free of back-and-forth communication](https://hackernoon.com/context-switching-and-why-developers-are-so-allergic-to-it).

Livecycle augments preview environments with [rich tooling](https://www.youtube.com/watch?v=Y71CkntF3Mc) for discussion, reviews, contextual feedback, and collaboration that are deeply integrated with [pull-request flows](https://thenewstack.io/welcome-to-the-prty-the-all-inclusive-pull-request/).

Using these tools, teams can break the silos between developers, product managers, designers, QA, and other stakeholders, and make the development process more collaborative and efficient.

![Liveycle experience](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3i5wv4v7fv15f0uyknfz.gif)

And while this is a great start, our ability to complete our mission depends on preview environments becoming even more mainstream than they are today.

### Embracing existing pipelines and tools

Livecycle's [managed preview environments](https://docs.livecycle.io/getting-started/livecycle-pipelines/) provide the equivalent of a minimal CI/CD pipeline tailored specifically for building ad deploying preview environments for every PR. They have minimal configuration, quick onboarding experience, and they [support static web or containerized applications](https://docs.livecycle.io/getting-started/livecycle-pipelines/supported-workloads).

While this flow worked well for some teams, and we got nice [early reviews](https://www.youtube.com/watch?v=K6MBe4laf9A), it wasn’t a perfect fit for everyone.

After gathering feedback from our users and customers, we soon realized that many teams prefer to use their own CI to avoid the overhead of managing another pipeline format and configuration.  
Futhermore, they wanted support for more complex environments, they had preferences for secrets management, storage, network configuration, and some even preferred to run the environments on their own compute infrastructure.

In short - it became clear that teams wanted more flexibility and control over their preview environments.

With that in mind, we decided to focus our SaaS product on our unique collaboration experience and to open-source the preview environment component of our product, redesigned with all the insights we’ve learned so far.

And that’s how Preevy was born.

## Building Preevy

Preevy is a CLI Tool that we designed to easily provision, manage, and expose ephemeral environments for containerized applications in the cloud.

These environments are inexpensive, easy to integrate, customizable, and accessible for both technical and non-technical users.

It’s simple, flexible, and doesn’t require deep DevOps knowledge, making it ideal for adding preview environments to your “pull/merge request” flows.

![Preevy experience](https://livecycle.io/8aa0a8b86e4ac965bd9dc100dc0bd9d3/terminal.gif)

As we designed Preevy, we thought deeply about the user experience, how to simplify the use of preview environments, and what tradeoffs would be most appropriate in the process.

### Exploring the problem space

There are well knows PaaS platforms such as Netlify and Vercel for example, that support preview environments with a simple, straightforward user experience.  However, when your application or deployment model doesn’t fit the exact nature of these PaaS solutions, it’s a very different story.

When that's the case, most companies choose to implement an [in-house solution](https://www.qovery.com/blog/state-of-preview-environments-report-2023) which often involves the use of [multiple tools](https://piotrminkowski.com/2023/06/19/preview-environments-on-kubernetes-with-argocd/) in order to get the job done.  

Yet, building a robust preview environments pipeline can be a non-trivial task.  
Challenges vary between different teams and applications, but we can group them into a few categories:

* **Resource management** - How do we provision the required resources for the preview environment? How do we make sure that we don’t exceed our budget?
* **Deployment pipeline** - How do we build and deploy the application? What about configuration and secrets management?
* **Access** - How do we expose our preview environments? Who is allowed to access them?
* **Data** - How do we seed or replicate data for the preview environment? What data should we use?
* **External services** - How do we interact with external services? Do we use the same configuration, or tenants?

Different teams tackle these challenges in different ways. Each approach has its own set of trade-offs and complexities. For instance:

* **For frontend applications** - it can be quite simple to implement preview environments with a simple static storage and a reverse proxy tool like [Nginx](https://nginx.org/) or [Traefik](https://github.com/traefik/traefik).
* **For cloud-native applications** - a [Helm](https://helm.sh/) based configuration with a CI/CD pipeline that deploy to a dedicated namespace in Kubernetes cluster and adjusts ingress rules for access can work well.
* **For serverless applications** - IaC tools such as [Pulumi](https://github.com/pulumi/pulumi)/[Terraform](https://github.com/hashicorp/terraform) can be used to provision a stack for every PR.

In some cases it can be a mix of the above with other tools included as well.

Ideally, we wanted Preevy to be suitable for most cases with minimal configuration, but still flexible enough for teams to adjust it to their needs. And while it would be impossible to cover all the different use cases, there’s one important tradeoff that is worth highlighting.

### Balancing fidelity

![The cost of Fidelity](https://livecycle.io/static/26098da31d5f8768c65e8219f5006061/17a7a/fidelity.png)

High fidelity in a preview environment is a much-coveted feature. Teams often desire an ephemeral environment that mirrors production as closely as possible – the same tools, same infrastructure, same configuration, same data – essentially, a clone of the production environment.

This line of thinking isn’t new and isn’t specific to preview environments; it’s common when trying to achieve high fidelity in pre-production (staging) and developer environments as well.

Developer environments are particularly interesting - while they are not necessarily ephemeral, there are many of them, and they are constantly used by developers as part of the development process.

While there are tools that are designed to make local development environments closer to production, these environments will never be identical. And that’s a good thing.

Developer environments need to be cheap, simple to troubleshoot, and flexible enough to support debugging, [live-reloading](https://blog.logrocket.com/complete-guide-full-stack-live-reload), frequent data teardown and migrations, testing, etc.

By contrast, real production environments of enterprise applications are inherently complex. They may depend on multiple SaaS/PaaS or other cloud solutions, they need to address scale, security, durability, availability, performance, compliance requirements, which makes them inflexible and costly to provision and manage.

When we say, “A developer’s dream is a DevOps engineer’s nightmare,” the opposite is also true. Developer productivity can be hampered by the complexities and inflexibility of “production-like” dev environments. These environments can be slow, prone to errors, difficult to run, debug, configure, maintain, customize, and troubleshoot, often becoming a nightmare for developers.

For these reasons, I believe most developer environments should prioritize developer experience over fidelity. Tools like [Containerized development environments](https://www.youtube.com/watch?v=DDv6wMJuIIY) and [cloud emulators](https://github.com/localstack/localstack) can strike the right balance and there’s no surprise that we see increased [activity](https://github.com/loft-sh/devpod) around [devcontainers](https://containers.dev/), and [similar](https://devfile.io/) [solutions](https://github.com/jetpack-io/devbox).

Preview environments fall on the same spectrum. They need enough fidelity for tasks like automated tests, product/design review, QA, but they should also be cost-effective and optimized for developer productivity. They can be implemented as a trimmed-down version of the production environment, or as a fine-tuned developer environment.

Each approach has its own pros and cons, and requires different skills, knowledge, and tools to execute. What’s crucial is that both work.

Fidelity is still important in preview environments, but we need to be mindful to the costs and complexities involved and make the right tradeoffs.

### How do we like our preview environments?

So, if fidelity is not our number one priority, what do we think are the most important factors when designing preview environments? 

The way we see it, Preview environments should be:

* Cheap: Preview environments should be very cheap to provision, not just in terms of $$$, but also in terms of time, effort and knowledge involved.
* Integrated: Preview environments should be used as a part of the development process and easily integrated with the existing flows and tools.
* Customizable: Preview environments should support customization, allowing teams to easily adjust them to their needs.
* Debuggable: Preview environments should enjoy great level of observability.
* Accessible: Preview environments should be easily accessible and designed to be used by technical and non-technical users a-like.

Preevy leverages Docker Compose definition and tooling, a shared profile storage, lightweight VMs, and a tunneling service to implement these principles. 

You can read more about how it works under the hood [here](https://preevy.dev/intro/under-the-hood).

When using the command `preevy up`, Preevy will, generate a preview environment name based on the current git branch, provision the required resources in the cloud driver, configure a remote Docker host, copy local volumes, build and deploy your application and expose its services to the end user using Preevy’s [tunnel server](https://livecycle.io/blogs/preevy-proxy-service-1/) (also OSS, deployed on `livecycle.run`) and print the urls in your terminal or in your [PR](https://github.com/livecycle/preevy/tree/main/packages/plugin-github-pr-link).

And that’s it, there’s no need to deal with IaC, registries, ingress, dns, or whatever, it just works.

Using `preevy ls` and `preevy down` you can list and destroy your preview environments.

The flow is very simple to run locally, or integrate in any CI/CD pipeline.

### The case for Docker Compose

Preevy designed to be simple to use and to bring ephemeral environments to the masses.

Docker Compose is a great fit for that, with several key advantages:

* Simplicity: Docker Compose [is easy to use](https://github.com/docker/awesome-compose), requiring no extensive DevOps knowledge.
* Popularity: Docker for desktop has successfully made containers accessible to a wider audience, and Docker tools, including Compose, are loved by [developers](https://survey.stackoverflow.co/2023/#section-admired-and-desired-other-tools)
* Minimal Tooling: Runs locally without the need for additional tools.
* Widespread Usage: Widely used and supported, it’s an established standard for describing multi-container apps.
* Customizability: The format is flexible to support different use cases and is easy to [extend](https://docs.docker.com/compose/compose-file/11-extension/) and customize.

It’s also worth mentioning the ease of Compose’s deployment model, that doesn’t require a registry, dedicated build pipeline, or image tagging and substitution.

### What about Kubernetes?

While Docker compose is a popular format for describing environment for multi-container application in local development or CI/CD jobs, it’s not commonly used for production environments due to it’s limitations and the popularity of Kubernetes, the de-facto standard for container orchestration at scale.

There are [heated emotions](https://twitter.com/petecheslock/status/1671536499748118532) and strong [beliefs](https://matt-rickard.com/kubernetes-maximalism) surrounding Kubernetes, but I’ll try to evaluate it solely from the perspective of ephemeral environments.

{% tweet 1671536499748118532 %}

When using Kubernetes, you usually need a combination of tools to create a new application deployment (even locally) and there’s a vast fragmentation of tools and approaches.

To get a similar experience of `preevy up`, first we’ll need to split the build and deploy using process or alternatively employ tools that orchestrate build-tag-push-update-sync flow like [Skaffold](https://skaffold.dev/)/[Tilt](https://tilt.dev/).

Afterwards, we’ll need a stable Kubernetes cluster or simple cheap way to provision Kubernetes clusters, a configured container registry, manage at least 5x the amount of configuration YAMLs and probably integrate it with additional tools such as Helm and Kustomize. `preevy up` behaves roughly the same in different CI pipelines or when run locally, but common Kubernetes pipeline tools behaves differently from local Kubernetes tools. They often utilize [GitOps practices](https://argo-cd.readthedocs.io/en/stable/operator-manual/architecture/) which favor a pull flow over a push flow and that usually requires additional set of tooling.

In addition, features that are useful for ephemeral/local environments like ‘depend-on’ and ‘volume mounts’ are significantly harder to implement with-in Kubernetes.

Even if we are already using Kubernetes in production, the path to preview environments is not always clear, and it’s usually not simple.

And therein lies the problem - Kubernetes is complex, application developers [don’t speak it](https://twitter.com/kelseyhightower/status/1671586507537416192), and it can be a huge barrier for teams that want to adopt preview environments.

{% tweet 1671586507537416192 %}

While devops and platform teams can abstract some of these complexities and streamline the experience to developers, not all organizations [have the resources](https://matt-rickard.com/dont-use-kubernetes-yet) to do so, and it’s not always high on their priority list.

That being said, Kubernetes’s ubiquity makes it great fit for a deployment target - it’s practically everywhere and support both cloud/on-prem environments.

That’s why we have a active effort in Preevy to support [Kubernetes as an infrastructure driver](https://github.com/livecycle/preevy/issues/90).

## The Road Ahead

We’re just getting started, and we believe we’re just scratching the surface of what’s possible to achieve with preview environments.  
Preview environments bridge the gap between code and application, between developers and other stakeholders, and between development and production.

Rich tooling around preview environments like Livecycle are example of how we can extend their functionality for [design and product reviews](https://livecycle.io/blogs/wilco). Looking ahead, we’ll want to extend the capabilities of Preevy with plugins that can help us integrate and embed different tools (bug reporting, tracing, feature flags, mocking, testing, code editing and beyond) and support more complex use cases of environment setup (data seeding, cloud resources, external services).

Preevy is still young, so if you’re interested in contributing or sharing feedback, don’t hesitate to reach us.

You can check our public [Roadmap](https://preevy.dev/roadmap) here, and don’t forget to leave a star :)

Here’s how easy it is to get started:

* Install Preevy (`npm install -g @preevy/cli`)
* Clone this [samples repository](https://github.com/dockersamples/awesome-compose) and `cd` into the sample you want to try. (or alternatively, use any other docker-compose based application)
* Login in to your cloud provider in your terminal (`aws configure/gcloud auth application-default login`)
* Run `preevy init`
* Run `preevy up`

  *Preevy currently require nodejs and docker to be installed on your device.*

If you got this far, thank you for your time and happy deploying!