Django development using Docker as host - Part 1: Dockerfile
Tags: django, docker, devops, devjournal
Published: 2021-01-17 07:27:01

> ### 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
1. Create `Dockerfile`
1. Add non-root user
1. Install runtime dependencies
1. Copy files to the container
1. Install python dependencies
1. 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/](https://docs.docker.com/get-docker/)
* **docker-compose**: [https://docs.docker.com/compose/install/](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"]
```