Django
Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel.
Docker
Docker is a set of
platform as a service
products that use OS-level virtualization to deliver software in packages called containers.
Recently while developing a Django project, I faced multiple issues installing the system dependencies (mysqlclient, etc) to install the required package for the working of my Django application in the local development environment.
This is always a hassle installing the system dependencies in the development machine, when
- The host is formatted
- You change your development machine
- Change of the operating system (say windows to Linux)
- Upgrading of operating system (removes support for the earlier version of libraries, always comes with the new version of libraries which may not have support for the application)
And again you start the race with the system setup, killing lots of time.
Even though we use Dockerfile
to create production-ready images to run the production server, why not use the same as host and remove the system dependencies with all phases of the development?
Using the Docker
as host, the release servers will always be in sync with the development machine and you can always look over the dependencies required for your application.
TOC
- Create
Dockerfile
- Add non-root user
- Install runtime dependencies
- Copy files to the container
- Install python dependencies
- Add
ENTRYPOINT
Let's jump in
If you have not already installed Docker
and docker-compose
on your machine, first install both by following the official guide.
- Docker: https://docs.docker.com/get-docker/
- docker-compose: https://docs.docker.com/compose/install/
1. Create Dockerfile
We need to first create a Dockerfile
in the project directory, where we will be creating the Django project.
To keep the image size lower, we will be using the latest slim python image python:3.9.1-slim
.
Add the following to the first line of the Dockerfile
FROM python:3.9.1-slim
2. Add non-root user
It is always recommended to create a non-root user instead of using the root user in the Dockerfile. So let's create a user by the name of the project (myapp).
ARG APP_USER=myapp
RUN groupadd -r ${APP_USER} && useradd --no-log-init -r -m -g ${APP_USER} ${APP_USER}
The ARG APP_USER=myapp
creates an argument variable so that the value myapp
can be referenced using the variable throughout the Dockerfile.
The next command adds a group and creates a user with the myapp
, creates home directory /home/myapp
for the user and assigns the permission to the home directory.
3. Install runtime dependencies
With Django, we are going to use the latest version of MySQL, so let's add the required system dependencies to the Dockerfile. Also, we will clean the Linux apt list as it is no longer required.
# default-libmysqlclient-dev -- Required for mysql database support
RUN set -ex \
# Runtime dependencies
&& RUN_DEPS=" \
default-libmysqlclient-dev \
" \
&& seq 1 8 | xargs -I{} mkdir -p /usr/share/man/man{} \
&& apt-get update && apt-get install -y --no-install-recommends $RUN_DEPS \
# Remove package list
&& rm -rf /var/lib/apt/lists/* \
&& mkdir /static_my_project
What above command does is
- Installs the
mysqlclient
system dependency - Removes the apt lists
4. Copy files to the container
Now, change the working directory to the /app/
directory
WORKDIR /app/
Let's copy the requirements.txt
file for the dependency instalment. (We will create it in further steps)
ADD requirements.txt /requirements.txt
Let's copy the application source code and the scripts to the container
COPY ./src /app/
COPY scripts/ /scripts/
5. Install python dependencies
Now we will install the required dependencies from the requirements.txt
file
RUN set -ex \
# Define build dependencies, they will be removed after build completes and libraries has been installed
&& BUILD_DEPS=" \
build-essential \
" \
&& apt-get update && apt-get install -y --no-install-recommends $BUILD_DEPS \
&& pip install -r /requirements.txt \
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $BUILD_DEPS \
&& rm -rf /var/lib/apt/lists/*
The above command does the following
- Installs the build dependencies
- Installs the python dependencies
- Removes the build dependencies
- Deletes the repository lists
Now we need to expose the port 8000
to access the running server
EXPOSE 8000
6. Add ENTRYPOINT
Let's add an ENTRYPOINT
which allows running manage.py commands without running the server directly. We will run the server by executing a command on the image. (I personally use this pattern because it gives you the flexibility to directly run Django shell without running the server).
ENTRYPOINT ["/scripts/docker/entrypoint.sh"]
That's it for the Dockerfile
. Here is the final content
FROM python:3.9.1-slim
ARG APP_USER=myapp
RUN groupadd -r ${APP_USER} && useradd --no-log-init -r -m -g ${APP_USER} ${APP_USER}
# default-libmysqlclient-dev -- Required for mysql database support
RUN set -ex \
# Runtime dependencies
&& RUN_DEPS=" \
default-libmysqlclient-dev \
" \
&& seq 1 8 | xargs -I{} mkdir -p /usr/share/man/man{} \
&& apt-get update && apt-get install -y --no-install-recommends $RUN_DEPS \
# Remove package list
&& rm -rf /var/lib/apt/lists/* \
&& mkdir /static_my_project
WORKDIR /app/
ADD requirements.txt /requirements.txt
COPY ./src /app/
COPY scripts/ /scripts/
# build-essential -- Required to build python mysqlclient library. https://packages.debian.org/sid/build-essential
RUN set -ex \
# Define build dependencies, they will be removed after build completes and libraries has been installed
&& BUILD_DEPS=" \
build-essential \
" \
&& apt-get update && apt-get install -y --no-install-recommends $BUILD_DEPS \
&& pip install -r /requirements.txt \
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $BUILD_DEPS \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["/scripts/docker/entrypoint.sh"]
Top comments (1)
Can you tell what is the difference between the RUN_DEPS RUN command and BUILD_DEPS RUN command technically? Could
default-libmysqlclient-dev
be used in the BUILD_DEPS RUN command as well?