Developing in Containers

I’d like to discuss a frustrating issue I find many software developers seem to run into. I think most do, myself included. After many months of struggling with this challenge, I have finally come across the perfect solution, and today I’d like to share that with you. Just before we get into that, let’s review what a running program is comprised of:

  • Code: The blueprints of your program.
  • Language Configurations: The language version and libraries.
  • An Operating System: The software powering your system.

You may already be sensing our challenge — that all the components listed above are available in a variety of options. These options are so variable that the number of possible configurations is scary to imagine.

Presenting the Issue

When we write code for a program, it’s usually intended to run on different machines and not just one we are using at that time. Since we know that we cannot develop in a production server, our challenge is that the code we write will respond differently when it matters most.

The problem is that in the environment we develop, we only have absolute control over one of the ingredients that make up software — the code itself. Of course, this is not by any means a new problem, and there are solutions out there that can help fix just part of the issue. However, this has always been the real challenge, and there was never a truly effective way to correct these issues efficiently, until recently.

Possible Solutions

As I mentioned, several solutions are available, but most only solve a single piece of this puzzle. For example, for language configurations in Python, you can utilize a virtual environment to specify which language version and libraries you want to use. The Operating System portion of your program can also be simulated using a virtual machine such as VMware. The challenge with these popular software solutions is that they each handle just one variant of your code composition and still don’t provide you with the settings you need to ensure your software will behave correctly in production. If you choose to tackle each of these variants on an individual basis, this can create a tedious amount of work and will demand more time, resources, and testing. Fortunately, there is now a much better way to resolve this issue.

Introducing Docker and Containers

In the most simplistic terms, Docker is a program used to run other programs. The magic of running your software through Docker is this — if you can successfully execute your program on Docker, you can rest assured it will work in almost any production setting just the same! In fact, all of the major IaaS (Infrastructure as a Service) providers utilize the Docker software to execute your program on their infrastructure today.

The best part is that getting started with Docker is even easier than you think, and this method can be used in the same way for projects of all shapes and sizes. Basically, we will take our existing project (the source code), create a simple configuration file for Docker (known as a “Dockerfile”), and instruct Docker to wrap it into a program that it will know how to run. We call this “containerizing your program.” This process will result in an Image file that is ready to run on any machine using Docker. Running in a container — how elegant.

Coding Directly In A Docker Container

What if we could take this convenient solution one step further? What if we could just code our program directly in a container — wouldn’t that be a treat? Well, the good news is you certainly can! Instead of writing your code locally, wrapping, containerizing, and executing it, this process would go straight to writing in the container itself.

First, let’s take a quick detour and look at the evolution of programming languages and programs. The designers of early imperative languages such as C and Java used a compiler when creating software. This would require you to first compile your code into a machine-readable language before running it. Flash forward to the younger days of Python, when its creators wisely leveraged the REPL method (Read, Evaluate, Print, Loop), where each line of code gets treated individually and is executed piece-wise — almost like it compiles and runs on the go. They achieved this because compilers became smarter as machines grew more powerful.

Similarly, we have gotten used to writing our code first, and containerizing it only after we have finished. Instead, we now have the tools and capability to simply develop our program directly in a container. This can ‘containerize’ incomplete or working projects piece-wise, enabling you to run them in an actual production setting. This will be no different than developing, debugging, and running code, just as you do so today. In the next section, I will share my setup, which can be applied to most configurations. But more importantly, it will demonstrate the ease of getting this all up and running.

Getting Started with Docker

The first thing we need to do is install Docker on our machine. You can download Docker for free at the following link: https://docs.docker.com/get-docker/

Please be aware that the Docker program can be very resource-intensive. Minimum requirements include a 64-bit processor and at least 4GB of memory.

Once you have Docker installed, the next step will be to choose a code editor of your preference. I personally use Visual Studio Code (VSC), as it’s very light, customizable, and backed by an awesome community of hard-working developers. Remember, it’s thanks to these wonderful communities that we have the extensions that exist today, which enable us to “develop in containers.”

Of course, there are many different code editors you can choose from, and you should work with the one that suits you the most. As I can’t cover the configuration of all of them, I will explain how this is done using VSC.

After you’ve installed VSC, you’ll need to download the following extensions: Docker and Remote-Containers. These can be found inside the VSC editor, on the left-hand side. Lastly, in the directory of your created project, you want to create the following:

  1. The Dockerfile (a text file that will provide your Docker with the necessary configurations to run your program).
  2. A folder (directory) named .devconatiner.
  3. A file named devcontainer.env, which should be placed in the .devcontainer directory. This file will store the environment variables for the container on which your program will run.
  4. A folder named .vscode.
  5. Finally, a file called settings.json should be placed in the .vscode directory. This includes the settings of your VSC editor within the container (such as text formatting).

For your convenience, I am sharing a repo with a simple sample folder that includes all of the above in the correct hierarchy: https://github.com/yogevsarel/NordicAPIDevelopInContainer/tree/main

And that’s it! You are now ready to code directly in a container that is already configured to work with Docker, meaning your completed software will work equally across any production server!

Summary

From my own experience, developing within containers has been a huge gamechanger — it’s the only way I code today. I’ve been developing software using this method for over a year now, leveraging multiple technologies and languages on various projects. I can’t even begin to explain the absolute joy it’s been to smoothly toggle between projects, seeing the magic of deploying programs on productions servers with virtually zero hiccups. Not to mention the crazy spaghettification that existed on my system before my transition (something I’m sure every senior developer can relate to).