Using persistent data in a dockerized Django development environment
Run Django inside a Docker container, mirroring your codebase, for a clean development environment that reflects code changes in real-time.
Development environment
Having a clean development environment where you can specify exactly which system and packages are being used is vital. It prevents interference between packages being installed on the host machine and those being used for your project. Docker containers provide a confined, clearly specified and reproducible environment and file changes on the host do not affect the container. However, during development, our code base changes continuously. To reflect these changes our container needs to be rebuilt and rerun continuously.
Djangos development server
Django runs its own development server, monitoring file changes and restarting when necessary. Shielded from the outside, these file changes do not happen inside a classical container setup.
Local database setup
Furthermore, working with a local database file like SQLite3 (Djangos default), all changes like migrations or database entries will be wiped clean every time the container is stopped (or restarted). Migrations can be rerun, even though it’s quite a tedious task. Other data will be lost completely.
Mounting a volume to a container
Mounting the project folder and database as a volume to the Docker container gives it read and write access. In other words, existing data (our project files and database) can be accessed and edited by the container, as well as new data can be saved permanently.
Setting up the environment
Our project structure
We want to mount the entire content of our project folder, including the database file we are using, for local development.
|-django_docker_setup
|---django_docker_setup
|---example_application
|-----migrations
|-manage.py
|-requirements.txt
|-Dockerfile
|-Database-File
Our Dockerfile
Building and running our container
Mounting a volume
Mounting a volume is the preferred mechanism to make data, used or generated by the Docker container, persistent.
In order to mount the folder to our container, we are using the -v or --volume flag
on the docker run
and specify the origin and destination paths:
docker run -v <Path of folder to mount>:<Path to mount inside container>
Publishing the port
In order to reach our Django application from outside the container, we need to publish a port to the host. In this case, we are mapping the previously exposed container port (8000) to the HTTP port (80) of our host. The format we are using is the following, more possibilities can be found in the Docker docs.
docker run -p hostPort:containerPort
The full command:
docker build -t container-name .
docker run -v $(pwd):/home/ -p 80:8000 container-name
Since our Dockerfile lives within the project folder, we want to mount all data from within that folder inside the working directory we have specified in the Dockerfile.
$(pwd)
prints the path to the current working directory/home/
is the absolute path, as defined in our Dockerfile
Accessing the running container
At this point, your Django project is running within your container. To access the container we can use docker exec
. It runs a command in a running container, allowing us to open a shell.
First, we need to find our container id, by running:
docker ps
which gives us something like this:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES3fe54fa54c56 container-name "/bin/sh -c 'python3…" 2 minutes ago Up 2 minutes 0.0.0.0:80->8000/tcp priceless_robinson
With the container id 3fe54fa54c56
we can now open our shell and run our migrations or whatever else we desire:
docker exec -it 3fe54fa54c56 bash
Inside we can now make or run our migrations in a persistent way:
dockeruser@3fe54fa54c56:/$ python3 manage.py migrate
Changing files during runtime
Also, code changes made during runtime will be mirrored inside the container in real-time. Changing a file outside of the container will instantly be registered by the Django application, and the development server restarts.
Summary
With these few simple steps, you created an environment that does not interfere with your host machine's setup, while keeping the development process smooth and easy. The environment is confined from your host’s system, clearly specified and reproducible. At the same time, file changes outside your container are instantly reflected inside, while changes inside, like migrations or database entries, are made persistent beyond the life of the currently running container.