Developing in Containers vs your “Day-to-Day” Machine

If you’re like me, you installed the docopt Python package on your dev machine ages ago to help juggle command line arguments for all your favourite scripts. So when you imported docopt and extended your public facing app’s CLI, it just worked. Fast forward to today when you pushed those changes without updating the documented dependencies, and cut a release. When a user reported trouble building your package, you realized the mistake and had to push a quick fix. You can console yourself that it’s not an end-of-the-world scenario, but it’s a bummer to have a broken release making it out into the world.

There are worse things that could have happened. You could have set more people back, or caused a critical system to go down. Thankfully you didn’t, but maybe it’s time to change your process. Maybe it’s time to start thinking about containers.

 

Containers, Reproducibility & The Development Process

Container tech like Docker can provide some big wins when it comes to deploying production applications and testing in a CI/CD environment. In those contexts, consistent and reproducible behaviour is a lot less work when you’ve got some container infrastructure in place. The appeal of a container often extends into the development context, as well. That’s because it’s relatively easy to spin up a “clean slate” (i.e., a new, generic container) to hack around on, or to provide curated jumping off points for a project. Given this, incorporating containers into the regular development flow starts to make sense. But what does that development flow really look like?

It turns out that developing from inside a container requires quite a bit of acrobatics. Just getting set up to write or change code in a container can be tricky. If a terminal-friendly editor like Vim or Emacs is your default, it’s easy enough to install one. But if you’re used to any customizations, you’ll also need to pull in your personalized configuration. Visual Studio Code has a great Remote SSH extension that lets you edit code in a container right from your day-to-day machine. But this kind of “add one more thing” approach is a slippery slope, and before you know it you’ve gone from a generic container to a proprietary one. 

Authentication from a container can be another tricky thing to tackle. Private SSH keys, for instance, aren’t something you want to share. If you don’t want to type a password each time you SSH somewhere or commit a change to your repository on GitHub, you’ll need to get those in order too. 

Automation can be used to navigate these problems, but it takes some extra effort. Once you’ve figured out the right approach, you can always write your own scripts or incorporate solutions like Puppet’s Bolt or Ansible to handle special cases programmatically. 

Containers can also be part of the development process without having to be the only place development happens, or even the development environment itself. They can simply be the place developers run tests against their local changes. With the right setup, dependencies for your test environment can be installed when a container is created. For applications with a small dependency footprint, the time it takes to regularly create a new container may be minimal. For larger dependency footprints though, getting a new instance started can be a time sink. If developers are in a place where the creation (and teardown) of their container is frequent, that time sink becomes a significant drain on resources.

Developing in a Container

As an alternative to the potentially time consuming create/teardown container workflow, you might consider maintaining persistent containers. However, when that’s the case, there’s real potential for containers to devolve into new machines with the same issues as their parents: just another place where every developer’s environment deviates. Even for a disciplined team devoted to maintaining unpolluted and reproducible containers, the work of maintaining development environments extends from every developer’s day-to-day machine (required to use those containers) to each instance of a persistent container deployed on it.

 

Reproducibility Without Containers

Development environments in containers are isolated from the underlying system that’s running them, but that’s not the only way to get things done. By comparison, at ActiveState, we’re working on language runtimes that, when deployed with our State Tool CLI, exist as standalone development environments within the systems that run them. From the outset, that means you can just start in on your day-to-day machine, the place where all your trusted aliases, editors, scripts, music players, browsers, etc. live.  

For Python developers familiar with the ActivePython or Anaconda distributions, the ActiveState Platform allows you to build your own distributions along those lines. ActivePython and Anaconda come pre-bundled with a broad range of packages, from Jupyter and SciPy, to docopt and PEP 8. If your project requires only a subset of those, or packages that ActivePython doesn’t include at all, you’re able to create something that’s a better fit for the target OS of your choice. 

Because ActiveState runtimes are pre-built for your target OS and architecture, there’s no guesswork around getting your project’s dependencies in place. Because the set of bundled packages is tested and built for you by the platform, any errors that would have caused a problem when building dependencies yourself are resolved preemptively.

The State Tool CLI has the capacity to be a lot of things, including secret manager, event handler, and script runner. But in relation to your runtime, it’s a tool to help you stay isolated from the rest of your system. It keeps your runtime scoped to the project you’re applying it to, while still benefiting from access to the overarching environment of the rest of your system.

 

Try Out the ActiveState Platform

If you don’t have one already, you’ll want to set up an account on the ActiveState Platform so you can create custom runtimes/distributions. Next you’ll want to install the State Tool so you can work from the command line.

From here, there are at least a couple of paths you can follow. You could get started by creating a project and building your own distribution from the ground up, adding just the packages that are important to you. Another tack would be to fork a tried-and-true ActiveState runtime like ActivePython-3.6 or ActivePerl-5.28. You can then either pare it down to something simple, or super charge it by adding even more of the packages you care about.

Once you have a distribution you’re happy with, you can use the State Tool to set it up and jump straight into coding your next project on your day-to-day machine, while enjoying all the benefits of containerized dev environment.

Shea Newton

Shea Newton

Experienced Software and Client Services Engineer with strong communication skills. Experienced in C, Python, and Rust. Holds a Bachelor's Degree in Computer Science from the University of Idaho and a Bachelor's Degree in Philosophy from the University of Oregon.