Developing in Python with Containers
Dependency management in modern Python development requires remembering to use & activating the correct virtual environments each time you want to work on a project. Using versions of Python other than what comes out of the box with the system additionally requires using distribution management tools like PyEnv or Anaconda (which also manages environments). If you are working on multiple projects at a time — very common in the Data Science world — it is easy to be logged into and install dependencies in the wrong environment and create a mess.
The new Dev Containers feature of Visual Studio Code makes it very easy to develop Python applications in an isolated Linux environment with all dependencies cleanly installed in a replicable way. You can develop inside a fully isolated Docker Container with just the correct programming language & dependency versions cleanly installed and any changes you make are re-built cleanly. Since most deployments these days are container based anyway, this allows your dev environment to mirror the deployment environment and reduce surprises.
Here we will look at how to use Dev Containers with Visual Studio code in a Windows machine. Developing on a Mac should be equivalent. This article assumes some basic working familiarity with Docker, Git, using the Linux Terminal & Python.
1. Install WSL2 and a Linux Distribution
Docker on Windows works much more efficiently if it can work with a WSL2 backend instead of having to use a full blown Virtual Machine. So if your version of Windows supports it, install WSL2 and a Linux Distribution on your development machine
2. Install Docker Desktop
The easiest way to get Docker on Windows with a WSL2 backend is to use Docker Desktop, so go ahead and install it in your Development Machine. It also comes with a mini Kubernetes single-node cluster if you would like to later test deploying your image with Kubernetes. Note that Docker Desktop is a commercially licensed product although it is free for personal non-commercial use & experimentation. So depending on your use case, you may need a license. Many large companies would have licensing agreements in place.
There are various ways to install Docker — which itself is open source — without Docker Desktop on WSL2. I have not tried them & YMMV.
3. Create your application folder with starter files
Here is my starter Github template that you can use to create your own repository. To use it, login to your Github account & click on the “Use this Template” button to “Create a new repository” and then git clone
it to your development machine inside a WSL terminal window. To avoid file corruption and performance issues, it is best to clone in somewhere within your WSL $HOME
(ie. cd ~
first) folder rather than the default Windows mounted folder that opens in the WSL terminal.
If you want to create your own starter files instead of using the template, you need the following 3 files:
1. Dockerfile
Here is a very bare bones Dockerfile that comes form the template. It starts with an official Python Docker image — in this case 3.11, and follows the standard practice of creating a working directory, installing dependencies, then copying over your app and running it from its entry point (customarily called app.py
)
FROM python:3.11
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["python","app.py"]
2. requirements.txt
This file specifies the dependencies for your application and is copied over & installed by Docker. The starter template just has a blank requirements.txt in which you can later add your dependencies.
3. app.py
This is your entry point that Docker will run after setup. In the starter template, this just prints “Hello World!” :-)
if __name__ == "__main__":
print("Hello World!")
4. Test that the Docker builds & runs
From your WSL terminal inside the folder, first build and run the image and ensure there are no errors. All going smoothly, you should see “Hello World!” on your terminal like in the screenshot below.
docker build --tag test
docker run test
5. Install extensions inside Visual Studio Code
At the minimum, you will need the WSL, the Dev Containers and the Python language support extensions. Install them from the VSCode market place. This is a one time configuration.
5. Open the folder in a VS Dev Container
First start a WSL Window in VS Code to ensure your VS Code is working in the Linux environment. Press F1
and type/select “New WSL Window”.
Then press F1
again and type/select “Dev Containers: Open Folder in Container…”, navigate to the applicaiton folder and open it.
At this stage, if you created your repository with the provided template , you should see something like the screen below showing VS Code is starting the Dev Container. This is because the starter repo already contains .devcontainer/devcontainer.json
file which tell VSCode to use our Dockerfile
to create the container.
If you created your starter files form scratch, you’ll see a screen like the one below asking how to configure the container. Select the optin “From Dockerfile” and you will get to the same screen as above. This step creates a .devcontainer/devcontainer.json
file in your folder that points to the Dockerfile for configuration — so this is a one time step.
5. Develop, test & iterate your app
To test or run your app, open up a terminal in VS Code ( Ctrl ~
) and type
python app.py
You should see the expected Hello World! output. If you are binding to a port inside the container (eg running a server), VS Code should automatically detect and allow you to expose it to the parent OS.
If you look at the working directory in the terminal listing, you will see it is not the same as the working directory we specified in the Dockerfile /app
but listing directory contents of /app
shows Docker has faithfully copied your files there as well — so what’s happenning here?
VSCode Dev Containers is using Docker primarily to setup the container config & mounts the folder you had in the orignal OS into your container as a volume mount. This lets you edit test & debug in the container but also copies over the changes in the original folder in the OS. Also, when we build & run Docker stand-alone, the changes will be carried over.
To test, let us change “Hello World!” to the German “Hallo Welt!”. Sure enough, running it gives the requried result.
if __name__ == "__main__":
print("Hallo Welt!")
Now let us try building and running within WSL terminal (not the container terminal inside VSCode). Sure enough, the changes are persisted and we see the expected German output.
5. Making Git work seamlessly inside dev container
Making Git work cleanly if you’re inside a container (so you can commit & push from VS Code) requires you to use a credentials helper if you are using HTTPS protocol to clone or to have an ssh-agent
running if you are using SSH. I normally use SSH, so to make ssh-agent
run automatically, add the following lines to your ~/.bashrc
if [ -z "$SSH_AUTH_SOCK" ] ; then
eval `ssh-agent -s`
ssh-add
fi
6. Re-building containers
If you change your dependencies, you want to re-build your container to ensure it is cleanly installed as part of the build process. Simply press F1
to access the menu and select Dev Containers: Rebuild Containers
. This runs really fast as most of the Docker image layers are already cached from the first builds.
Bonus
If you want to develop a Flask Application in Docker container, a very common use case, you can use the Flask based version of the starter template. It creates a starter Flask app that writes Hello World on the default route. When you run it, VS Code should automatically detect the open port and offer to expose it to Windows and open a Browser.
All the best developing with the convenience of containers!