.. index:: pair: News ; 2018-01-29 pair: Dockerfile ; Adam King pair: Adam ; King .. _a_simple_recipe: ======================================================================================= **A Simple Recipe for Django Development In Docker** par Adam King (Advanced tutorial) ======================================================================================= .. seealso:: - https://medium.com/@adamzeitcode/a-simple-recipe-for-django-development-in-docker-bonus-testing-with-selenium-6a038ec19ba5 .. contents:: :depth: 3 .. figure:: adam_king.png :align: center https://medium.com/@adamzeitcode/a-simple-recipe-for-django-development-in-docker-bonus-testing-with-selenium-6a038ec19ba5 .. _dockerfile_adam_king: 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 :file:`/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. .. _dockercompose_adam_king: docker-compose.yml Adam King ================================ .. code-block:: yaml 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: .. code-block:: yaml 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