DEV Community

jess unrein
jess unrein

Posted on

Launching a django webserver in Docker

Every single time I have to set up Docker for a new web server I forget a couple key things on how to set it up. This little post is mostly a note to my future self that this is how you do it and why.

For a detailed breakdown of common keywords and concepts of putting together a Dockerfile, check out my previous post on Dockerizing a simple python process.

I found myself needing to run a django server on Docker today because it turns out that you can't set memory limits using the python resource library on processes on macOS since about version 10.14. Solution? Run it on a Python Docker Image that runs on Linux. Easy enough.

FROM python:3.11
RUN pip install poetry
COPY . .
RUN poetry install
CMD ["python", "./myapp/manage.py", "runserver"]
Enter fullscreen mode Exit fullscreen mode

Quick and dirty. I could probably be more careful with what I'm copying, where, and such. But I'm in a rush and this will do the job.

docker build . -t myapp
Enter fullscreen mode Exit fullscreen mode

Builds just fine, I see poetry installing the packages that it should. Beautiful!
However, when I try to run the image...

docker run -it -p 8000:8000 myapp
Traceback (most recent call last):
  File "//./myapp/manage.py", line 11, in main
    from django.core.management import execute_from_command_line
ModuleNotFoundError: No module named 'django'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "//./myapp/manage.py", line 22, in <module>
    main()
  File "//./myapp/manage.py", line 13, in main
    raise ImportError(
ImportError: Couldn't import Django. Are you sure it's installed and available on your PYTHONPATH environment variable? Did you forget to activate a virtual environment?
Enter fullscreen mode Exit fullscreen mode

That's annoying. I just saw poetry install all the libraries. And yes, I went and checked, and django is present in my poetry.lock and pyproject.toml files. What gives? Seems like poetry is creating a virtualenv somewhere that needs to be activated, but that's really not necessary since Docker is already isolating the project and having a virtualenv is redundant. So let's try add a poetry config command to the Dockerfile.

FROM python:3.11
RUN pip install poetry
COPY . .
RUN poetry config virtualenvs.create false --local
RUN poetry install
CMD ["python", "./cohortcreation/manage.py", "runserver"]
Enter fullscreen mode Exit fullscreen mode

Now, the --local tag isn't strictly necessary here, because, again, Docker is isolating this project. But just in case I copy paste this somewhere where it might mess with poetry configurations, let's make sure this config value stays local to whatever project I'm working with.

Next, rebuild the docker image. Loads just fine. Let's try running it.

docker run -it -p 8000:8000 myapp
2023-12-05 22:33:32,585 INFO autoreload django.utils.autoreload Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
December 05, 2023 - 22:33:32
Django version 4.2.4, using settings 'myapp.project.settings'
Starting ASGI/Daphne version 4.0.0 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
2023-12-05 22:33:32,806 INFO server daphne.server HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2023-12-05 22:33:32,806 INFO server daphne.server Configuring endpoint tcp:port=8000:interface=127.0.0.1
2023-12-05 22:33:32,807 INFO server daphne.server Listening on TCP address 127.0.0.1:8000
Enter fullscreen mode Exit fullscreen mode

Hey! That looks like what I'd expect from running ./manage.py runserver on an average django project. Success! Now I just need to open my browser and get on with my testing.

Empty response from localhost oh no!

What? No. Why? I set up the ports when running the container. I even remembered this time to add the -p 8000:8000 flag to the run command the first time! I even remembered that the first port listed is for what port you want to access on the host, and the second port is the port you want Docker to forward, even though that always feels backwards to me.

Okay, let's check the container and see what's up.

docker ps

. . .

d8ccf5c1aa7a myapp "python ./myapp/ma…" 6 seconds ago Up 5 seconds 0.0.0.0:8000->8000/tcp priceless_pascal
Enter fullscreen mode Exit fullscreen mode

That all seems correct. Should be able to access localhost:8000 to get to whatever port 8000 on the docker container is doing. So what's happening here?

I kill the container, run the webserver in a different terminal tab, just to make sure I'm not going crazy. It loads fine. I keep the local server running, but also start up the docker container again, and now I can't access the app front end through the browser anymore. So clearly docker is doing something with port 8000. But letting me access my server isn't it.

I'll spare you the roughly three thousand google searches I did before arriving at the answer. Long story ~short~ somewhat less long, Python thinks we're exposing our API at 127.0.0.1:8000. This is what we want if we're just running the server locally. However, if we look at the ports output from docker ps, we're not exposing 127.0.0.1:8000. We're exposing 0.0.0.0:8000. One last tweak to the Dockerfile should do the trick.

FROM python:3.11
RUN pip install poetry
COPY . .
RUN poetry config virtualenvs.create false --local
RUN poetry install
CMD ["python", "./myapp/manage.py", "runserver", "0.0.0.0:8000"]
Enter fullscreen mode Exit fullscreen mode

Building the image works. Running the image works. Now I just need to cross my fingers and check my browser.

Django Admin Panel Header

Thank god. Time for a nap.

Top comments (3)

Collapse
 
mellen profile image
Matt Ellen

Great breakdown of the problem. I hope I remember it next time I'm scratching my head over docker.

Collapse
 
kwnaidoo profile image
Kevin Naidoo • Edited

This is a good start. Just some suggestions:

A virtual environment will probably work a lot better unless, of course, you have docker-compose with multiple services.

Gunicorn is probably better than "run server" inside a container. This will give you more of prod-like server.

gunicorn -w 2 -b 127.0.0.1:8000 djangoapp.wsgi:application
Enter fullscreen mode Exit fullscreen mode
Collapse
 
thejessleigh profile image
jess unrein

Yup - as I explain at the beginning of the post, this was a quick and dirty solution to get around an operating system limitation, not a tutorial on production-like builds. Also, fwiw, I would recommend using uvicorn over gunicorn since it supports both wsgi and asgi.