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!).
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