A Simple Recipe for Django Development In Docker par Adam King (Advanced tutorial)

Dockerfile Adam King

# My Site
# Version: 1.0

FROM python:3

# Install Python and Package Libraries
RUN apt-get update && apt-get upgrade -y && apt-get autoremove && apt-get autoclean
RUN apt-get install -y \
    libffi-dev \
    libssl-dev \
    libmysqlclient-dev \
    libxml2-dev \
    libxslt-dev \
    libjpeg-dev \
    libfreetype6-dev \
    zlib1g-dev \
    net-tools \
    vim

# Project Files and Settings
ARG PROJECT=myproject
ARG PROJECT_DIR=/var/www/${PROJECT}

RUN mkdir -p $PROJECT_DIR
WORKDIR $PROJECT_DIR
COPY Pipfile Pipfile.lock ./
RUN pip install -U pipenv
RUN pipenv install --system

# Server
EXPOSE 8000
STOPSIGNAL SIGINT
ENTRYPOINT ["python", "manage.py"]
CMD ["runserver", "0.0.0.0:8000"]

Without getting too deep in the weeds about creating Dockerfiles, let’s take a quick look at what’s going on here. We specify some packages we want installed on our Django server (The Ubuntu image is pretty bare-bones, it doesn’t even come with ping!).

WORKDIR

The WORKDIR variable is interesting in this case it’s setting /var/www/myproject/ on the server as the equivalent to your Django project’s root directory. We also expose port 8000 and run the server.

Note that in this case, we’re using pipenv to manage our package dependencies.

docker-compose.yml Adam King

version: "2"
services:
  django:
    container_name: django_server
    build:
      context: .
      dockerfile: Dockerfile
    image: docker_tutorial_django
    stdin_open: true
    tty: true
    volumes:
      - .:/var/www/myproject
    ports:
      - "8000:8000"

Now we can run docker-compose build and it’ll build our image which we named docker_tutorial_django that will run inside a container called django_server.

Spin it up by running docker-compose up.

Before we go any further, take a quick look at that docker-compose.yml file. The lines,

stdin_open: true, tty:true

stdin_open: true
tty: true

are important, because they let us run an interactive terminal.

Hit ctrl-c to kill the server running in your terminal, and then bring it up in the background with docker-compose up -d

docker ps tells us it’s still running.

docker-compose up -d

We need to attach to that running container, in order to see its server output and pdb breakpoints. The command docker attach django_server will present you with a blank line, but if you refresh your web browser, you’ll see the server output.

Drop:

import pdb; pdb.set_trace()

in your code and you’ll get the interactive debugger, just like you’re used to.

Explore your container (docker-compose exec django bash)

With your container running, you can run the command:

docker-compose exec django bash

which is a shorthand for the command:

docker exec -it django_server bash.

You’ll be dropped into a bash terminal inside your running container, with a working directory of /var/www/myproject, just like you specified in your Docker configuration.

This console is where you’ll want to run your manage.py tasks: execute tests, make and apply migrations, use the python shell, etc.

Take a break

Before we go further, let’s stop and think about what we’ve accomplished so far.

We’ve now got our Django server running in a reproducible Docker container.

If you have collaborators on your project or just want to do development work on another computer, all you need to get up and running is a copy of your:

  • Dockerfile
  • docker-compose.yml
  • Pipfile

You can rest easy knowing that the environments will be identical.

When it comes time to push your code to a staging or production environment, you can build on your existing Dockerfile maybe add some error logging, a production-quality web server, etc.

Next Steps: Add a MySQL Database

Now, we could stop here and we’d still be in a pretty good spot, but there’s still a lot of Docker goodness left on the table.

Let’s add a real database.

Open up your docker-compose.yml file and update it:

version: "2"
services:
  django:
    container_name: django_server
    build:
      context: .
      dockerfile: Dockerfile
    image: docker_tutorial_django
    stdin_open: true
    tty: true
    volumes:
      - .:/var/www/myproject
    ports:
      - "8000:8000"
    links:
      - db
    environment:
      - DATABASE_URL=mysql://root:itsasecret@db:3306/docker_tutorial_django_db

  db:
    container_name: mysql_database
    image: mysql/mysql-server
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=itsasecret
    volumes:
      - /Users/Adam/Development/data/mysql:/var/lib/mysql

db

We added a new service to our docker-compose.yml called db.

I named the container mysql_database, and we are basing it off the image mysql/mysql-server. Check out http://hub.docker.com for, like, a million Docker images.

MYSQL_ROOT_PASSWORD

We set the root password for the MySQL server, as well as expose a port (host-port:container-port) to the ‘outer world.’ We also need to specify the location of our MySQL files. I’m putting them in a directory called data in my Development directory.

In our django service, I added a link to the db service. docker-compose acts as a sort of ‘internal DNS’ for our Docker containers. If I run docker-compose up -d and then jump into my running Django container with docker-compose exec django bash, I can ping db and confirm the connection:

root@e94891041716:/var/www/myproject# ping db
PING db (172.23.0.3): 56 data bytes
64 bytes from 172.23.0.3: icmp_seq=0 ttl=64 time=0.232 ms
64 bytes from 172.23.0.3: icmp_seq=1 ttl=64 time=0.229 ms
64 bytes from 172.23.0.3: icmp_seq=2 ttl=64 time=0.247 ms
64 bytes from 172.23.0.3: icmp_seq=3 ttl=64 time=0.321 ms
64 bytes from 172.23.0.3: icmp_seq=4 ttl=64 time=0.310 ms
^C--- db ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.229/0.268/0.321/0.040 ms
root@e94891041716:/var/www/myproject#

DATABASE_URL

Adding the environment variable, DATABASE_URL=mysql://root:itsasecret@db:3306/docker_tutorial_django_db Will allow our Django database to use a real, production-ready version of MySQL instead of the default SQLite.

Note that you’ll need to use a package like getenv in your settings.py to read environment variables:

DATABASE_URL=env('DATABASE_URL')

If it’s your first time running a MySQL server, you might have a little bit of housekeeping: setting the root password, granting privileges, etc.

Check the corresponding documentation for the server you’re running. You can jump into the running MySQL server the same way:

$ docker-compose exec db bash
$ mysql -p itsasecret
> CREATE DATABASE docker_tutorial_django_db;
etc, etc