Developer environments have existed for some time, many of us remember trying to emulate expensive web hosting environments on our personal machines, and as they often varied so much, hitting the infamous “works on my machine” problem. We found and used tools such as virtual machines and vagrant to help mitigate these issues on local machines. We lovingly tended remote development environments to help us ensure that our code worked on something vaguely resembling a production environment before we pushed changes live.
We are now in something of a transition phase, and many development teams still maintain different environments for their workflow. These are typically something like “development”, “testing”, “staging”, and “production”, but the names are not the important part. That they remain the same for a long time, and are long-lived or static is important. Maintaining static environments for particular purposes such as testing is better than not maintaining them, but are counter-intuitive if you want to switch to a continuous development workflow.
Before moving any further, it’s worth clarifying what an environment could be. If your application has a monolith architecture, then an environment needs to mimic your entire application. If you are using a microservices architecture, then an environment could be just one application component. In the case of microservices, that environment likely also needs ephemeral support services to properly test code changes, such as a pre-seeded database, or a messaging bus.
While it depends on how you initially created the environments, and if you used any form of “infrastructure-as-code” tooling, long-living static environments can fall out of synchronization with each other. This time leads to different dependency versions and bugs that are due to environment quirks and not necessarily code changes.
The costs of maintaining one or more static environments that sit idle for a long time can add up. These costs are not only in infrastructure costs, but also in the time your team members spend fixing, maintaining, and updating the environments.
Static environments hold up teams from moving quickly. If one environment can only run and maintain one build and test context at once, this leaves other developers waiting for that to complete before they can move forward. If you have a large team, this wasted time adds up.
A continuous development workflow focuses on speed and flexibility of change. Having such an immovable and inflexible bottleneck is something anyone wanting to adopt the practise fully should address.
Sometimes referred to as “Dynamic environments”, “Temporary environments”, “on-demand environments”, “Sandbox environments” or “short-lived environments”, Ephemeral testing environments mimic a production environment (and keep in sync with it) for building and testing feature branches.
If you’re not already using infrastructure-as-code, and/or virtual machines or containers, consider using them, as it makes using ephemeral environments easier. There are also services (Humanitec included) that can create environments for you as you push code changes, meaning you have a specific environment ready and waiting for you to test with.
Keeping your infrastructure as code and reproducible resources means you can recreate the same environments repeatedly, and keep the configuration of your environments identical, aside from different environment and configuration variables.
Generally attached to branches, a developer typically triggers the creation of a new environment by pushing to the branch, and other services (internal and external) automatically run some of the following tasks depending on the nature of your application:
If all the tests pass, then you can automatically or manually approve the changes and merge them. If you later practise continuous deployment, these changes are now deployed into a production environment.
With the environment no longer needed, and the branch merged, remove the environment, and the resources it used. Whenever you merge the changes into a mainline branch, you should run further automated tests to ensure that other code changes don’t affect these new changes.
To make rolling back problematic builds easier, some teams use blue-green deployments. This technique involves running two production environments and switching where you deploy production builds for each deployment. This means that if the deployment fails or has problems, you can switch traffic to the last good environment while you debug what went wrong.
Blue-green deployments existed for some time, and containers and Kubernetes make using it easier, or makes it easier to create more flexible production deployment options.
Some development teams hide features behind flags they can enable for users that match certain criteria instead of releasing different builds to different users. This is an especially common technique for larger software projects, where product teams want to experiment with multiple features at once.
Canary testing and dark launches are relatively similar methods of bundling together one or more features you want to test. Canary launches mostly focus on testing the user response to back end changes, while dark launches focus on front end changes.
The advantages of ephemeral environments reflect the negatives of static ones.
While the cost of constantly creating new environments can add up, you have more control and visibility of those costs, and there is less long term cost from team members maintaining those environments.
Even though build and test processes in individual environments can still take some time to run, It’s also hard to calculate the cost savings of team members not having to wait for environments to be available before pushing their own changes. Multiple parallel environments mean that multiple build and test processes can happen at once, so at least developers are not all waiting for the same build to complete.
Ephemeral environments also open your team up to new build strategies that were not available before. These include generating parallel builds for different software versions (for example, Windows, Linux, macOS), rapid prototyping of feature variants, or different datasets, and preview builds for public or private beta or insider programs.
Every environment created is a clean environment, with no legacy artifacts, dependencies, or quirks. You can depend on each build starting from the same state as every other.
Kubernetes is an ideal tool for creating ephemeral environments as it lets you define not only the baseline infrastructure you need (the container(s)), but also the code to run, and most crucially, the support infrastructure needed to test your changes fully.