Using Docker Instead of Virtual Environments (venv)

I was talking to a co-worker recently about my use of docker instead of virtual environments (venv) and he mentioned that I should probably write something up about it. It didn’t occur to me that this might be a more preferred method than venv until I had this discussion.

It makes sense to briefly explain venv and then how I use docker instead.

What Are Virtual Environments (venv)?

I think this is a great place to start. If you’ve ever used Python, you’ve probably seen some mention of virtual environment (venv). I won’t go too crazy on explaining them and how to use them. In short, venv allows you to create a Python environment that is self contained to help you not clutter up your machine with various libraries. Worse yet, if you need to run multiple versions of the same library, you can have issues running everything in one environment. For the real details, I suggest you check out the Python documentation on venv.

Using Docker for Your Virtual Environment

I won’t spend much time at all talking about docker itself because there’s endless documentation on it. I personally like using docker as my virtual environment for just about everything for a number of reasons:

  1. I don’t clutter up my development machine
  2. I can run code in a very specific envrionment
  3. I can run code in what I plan to use in my target environment

As a stretch item, it also allows me to make sure I can bring up the environment from scratch like I would during deployment. There’s been too many times on my local machine where I’ve installed some random library/application and forgot about it. During production deployment, I learn pretty quickly what I forgot.

You can even use a Dockerfile to make sure the same exact environment is rebuilt every time too.

How Do I Use Docker Instead of venv?

I’m glad you asked because that’s the whole point of this post! It’s quite simple, I usually just run a command similar to the below one:

docker run -it -v /home/path/to/source/code:/opt/app python:3.8 /bin/bash

This command starts an interactive Python 3.8 docker container. The -it says to run it interactively and allocate a TTY. The trailing /bin/bash says to run bash. In short, we get a bash command prompt in this Python container. The -v /home/path/to/source/code:/opt/app tells docker to mount the /home/path/to/source/code directory into /opt/app of the docker. You need to make sure that /home/path/to/source/code is the full path to the directory and not a relative path.

Once in the docker container, you can cd /opt/app and run python blah.py (or whatever Python file you have in there). Since /opt/app is a mount of my local filesystem, I can make code changes using vscode or whatever editor I prefer. Those changes are live in the docker as soon as I save this them.

Adding Libraries

The nice thing about running this in docker is that I can issue pip install commands to bring in any new modules/libraries and they will only be installed in the docker itself. The bad news is that if you exit the docker, those libraries are lost. This is why I typically will add a requirements.txt file in my mounted directory so that I can simply do a pip install -r requirements.txt any time I want to start from scratch.

As noted above, this is a great way to make sure you have included all of the proper libraries for your code to function in production. In addition, you won’t have to worry about accounting for multiple versions of a library due to dependencies for other applications you are developing (Yes, I know this is also the point of venv but docker seems less confusing to me).

Testing

Making sure you have everything you need from the ground up is nice from a dependency perspective. This is test #1 by starting the docker container over again from scratch. For code level testing, I like to also include pytest and then run those from a scratch start of the container too. You can dig up some examples of pytest writing in my Writing Tests For Your Python Project.

All of this makes sure I didn’t set/rely upon some environment variable or global variable that was set some where else.

Wrapping Up on Virtual Environments (venv)

Yes, venv is a tried and true method widely accepted Python developers. I won’t argue this point at all. For me, using venv becomes just too clunky and confusing. Using Docker instead of virtual environments (venv) seems less clunky to me. This is why I use docker and just mount my code instead.

The other added benefit of using docker is that I’m able to later build a Dockerfile of the entire environment. This makes everything portable to AWS, Kubernetes, or some other developer’s machine.