DEV Community

Mike
Mike

Posted on • Edited on

Python development environment in a Docker container

Purpose

Create and setup a python development environment inside of docker in 5 minutes. (Also more or less a journal to myself of how to do this)

What to know

You will need to understand virtual environments with python. I will place the commands that are needed for setting up the virtual environment but will not expand upon them.

What you need

Python 3.8 is the version I'll be using in this tutorial.
Python modules you will need are:

  • fastapi
  • hypercorn

a few files will need to be created.

  • main.py
  • Dockerfile
  • .dockerignore
  • docker-compose.yml

The following commands will install and create the files you need.

mkdir -p ~/projects/myproject
cd ~/projects/myproject
touch main.py Dockerfile .dockerignore docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

Above we create the project directory and the initial files we'll need for the app.

python3.8 -m venv .venv
source .venv/bin/activate
pip install fastapi hypercorn
pip freeze > requirements.txt
Enter fullscreen mode Exit fullscreen mode

This code above creates the virtual environment in the CWD.
In the above commands ~/projects/myproject should be whatever directory you want to put your code into.

The files

Now to modify the files you just created. Lets start with the fastapi application.

in main.py:

from fastapi import FastAPI

app = FastAPI()

@app.get('/')
def index():
    return {'key' : 'value'}
Enter fullscreen mode Exit fullscreen mode

The above code sets up a very basic fastapi application.
First you import FastAPI from fastapi
next you instantiate the app.
then you decorate your index with @app.get('/')
and finally you return a dictionary.

Next in Dockerfile:

FROM python:3.8-slim

WORKDIR /app/
ADD requirements.txt /app/

RUN pip install -r requirements.txt

ADD . /app/

EXPOSE 8005

CMD ["hypercorn", "main:app", "--bind", "0.0.0.0:8000", "--reload"]

Enter fullscreen mode Exit fullscreen mode

The above file creates a configuration for docker. It pulls a base image from the dockerhub. This uses python:3.8-slim as the base image. Next we set our Work Directory to /app/ in the container.
after that we add only our requirements.txt to that app directory. then we can run pip install inside the container to install our app requirements, and setup our environment inside the container. After that we add the rest of our app to our /app/ directory on the container expose the port we want to be able to connect to this on and run hypercorn which was installed when we did pip install -r requirements.txt

Now for .dockerignore:

you don't have a need to include your .venv folder because you will be creating a virtual environment in the container.

Also you don't need the pycache directories on the container(waste of space).

.venv/
__pycache__/
Enter fullscreen mode Exit fullscreen mode

Now for docker-compose.yml

which will put it all together for us.

Note:

if you copy and paste and have issues with the .yml file you may need to check and see if the indentations are spaces or tabs. If they are tabs they will need to be converted to spaces.
It's a yaml thing..

version: '3.8'
services:
    api:
        build: .
        image: fastapiapp:latest
        ports:
            - 8005:8000
        volumes:
            - type: bind
              source: .
              target: /app/
Enter fullscreen mode Exit fullscreen mode

Ok this is the last file. This file holds the container build/config instructions. It will also in this instance build the image for us. It will read the Dockerfile in the current working directory and build and app called fastapiapp with the tag latest

To run it all and see some progress, you should now be able to run docker-compose buid && docker-compose up -d and it should build and bring up your docker development environment. The first time it runs it may take a minute or to to come up, but after that it should be utilizing the build cache in docker to only rebuild/add the code that you modified. It will be accessable from 127.0.0.1:8005

I chose port 8005 because I have containers running on the previous 5 ports. You can use in the Dockerfile and docker-compose.yml file what ever port you'd like. The mapping of the ports is hostport:containerport.

Bind mount volumes are used in this instance to allow you to modify the code in the current directory and it should also update in the container at the same time. The only time you will need to make a build is when/if you add new python modules. Because they will need to be recompiled and installed into the container. So also remember to run another pip freeze > requirements.txt if you install new modules.

A couple of helper scripts to make the process easier.
build.sh
with the contents of:

docker build --pull --rm -f "Dockerfile" -t fastapiapp:latest "."
Enter fullscreen mode Exit fullscreen mode

And a script for running the container in up.sh

docker-compose -f "docker-compose.yml" up -d --build
Enter fullscreen mode Exit fullscreen mode

Things to check when it all goes wrong.

  • Is port 8005 open on your firewall at least for the local connection?
  • Is the container running?
  • - docker ps the image name should be fastapiapp:latest if it is listed without running docker ps -a then it is running.
  • If it's not running you need to check the logs docker logs -f container_name generally you can see the issue pretty clearly.

github repo

All of the above mentioned files will be in a repository on my github.

Top comments (9)

Collapse
 
clawsicus profile image
Chris Laws

In the first code example you create a virtual environment but do not activate it. This would result in you installing fastapi and hypercorn into the system python which is probably not what you wanted.

Collapse
 
mikecase profile image
Mike

Aye, you're right. Thanks for pointing that out.

Collapse
 
gwynevans profile image
Gwyn Evans • Edited

Don't you mean "source .venv/bin/activate", not "source .venv/bin/python"?

A couple of further minor points - your (optional?) "EXPOSE 8005" line should be "8000", shouldn't it? There's also a typo in the "docker-compose" line where you have "buid" rather than "build".

Collapse
 
lewisblakeney profile image
lewisblakeney

This is a game-changer! Using Docker for Python development is a brilliant idea. This setup will streamline workflows and ensure consistency across environments. A must-try for any Python development services!

Collapse
 
maanuanubhav999 profile image
anubhav_sharma

Thanks this was helpful.

Collapse
 
cjsmocjsmo profile image
Charlie J Smotherman

Ok, makes sense now. Thanks for the feedback :)

Collapse
 
mikecase profile image
Mike

Glad I could help. I will try to make the post a little more clear about that, I can see from the title now that it's kind of misleading.

Collapse
 
cjsmocjsmo profile image
Charlie J Smotherman

Why create a venv inside a container? I mean your python environment is already isolated by the container, why introduce a not needed layer of abstraction? Just curious :)

Collapse
 
mikecase profile image
Mike

You're not actually setting up a virtual environment inside the container. The container ultimately ends up being your end product. You can write your code and build your container and push it to the cloud/your server/github/dockerhub etc. It effectively sets up a CICD workflow. I will try to make this clearer in the post above but if you look at the Dockerfile you're not actually setting up a virtual environment inside the container. The only thing you do inside the container is install the required modules for your app to run and updating your codebase. Once your push that container to your server for example it can be live. No need to setup any kind of environments on your server it's all containerized.