Hello everyone! (`・ω・´)ゞToday I will show you the easiest way to set up a Django development environment using Docker.
I actively use Docker in my web application development because it provides an easy and clean development environment setup. I recommend all developers to use Docker, but what I find on the Internet is that many tech blog articles on Django Dockerization are not specific to the development environment, but to the production environment, i.e., what you need for local development. It is to include nginx and gunicorn configurations that are not needed.
These settings are excessive in a development environment. In the development phase, only the Django and database parts need to be containerized, and a local server is sufficient for the web server. This minimal configuration allows for quick setup and prevents the local database from becoming so bloated with past development data that you don't know where everything is.
So in this article, I will summarize my simple setup of a development environment using Docker. The files to create are a Dockerfile
and a docker-compose.yaml
file.
So let's get started!
Considerations:
I am using pycharm for development.
1. Create a Django project
To get started, create a new folder and name it django_10
. This folder should be empty. Here's an example of what it looks like in PyCharm:
Please note that External Libraries
and Scratched and Consoles
can be ignored in the screenshot.
Next, create a new project within emrpty folder(django_10
in this example).
Assuming Django has already been installed locally via pip (Python's package installer), enter the command as accompanying box:
django-admin startproject blog .
The word "blog" here is an arbitrary name. This word will be the project name.
Also, the important thing here is **to put a period after the project name**(in this case, blog).
If we don't to put a period, The project folder structure is not properly constructed.
If the previous command worked, you should see a screen as accompanying image.
2. Create Dockerfile
and docker-compose.yaml
files
The next step is to create a Dockerfile
and a docker-compose.yaml
file.
Below are the contents of these files.
- The
workdir
in the Dockerfile specifies the working directory of the Docker environment. If theworkdir
is duplicated with other Dockerfiles, an error message will appear and the file will not work. Therefore, please create a new, original location for this Docker file. - This Docker environment uses PostgreSQL as the database. Therefore, we will need to adjust the database settings in
settings.py
and the database adapter inrequirements.txt
for PostgreSQL later.
FROM python:3.9
ENV PYTHONUNBUFFERED 1
WORKDIR /blog #1
COPY requirements.txt /blog/requirements.txt
RUN pip install -r requirements.txt
COPY . /blog
CMD python manage.py runserver 0.0.0.0:8000
3. The database service name in the docker-compose.yaml
file will be the hostname in settings.py
. This setting is often overlooked, but is an important part of the process. I will explain this part later.
4. Regarding ports
, use different port numbers for the left and right sides. For the right side, the default port:5432
for Postgres is good. For the left side, use a number other than 5432
.
5. Put the environment
section at the end of docker-compose.yml
and set the PostgreSQL superuser
password. We will add the actual password in the .env
file later.
version: "3.9"
services:
backend:
container_name: blog
build: .
volumes:
- .:/blog
ports:
- 8000:8000
depends_on:
- db_blog
db_blog: #3
container_name: db_blog
image: postgres #2
restart: always
volumes:
- .dbdata:/var/lib/db_blog
ports:
- 5451:5432 #4
environment: #5
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
3. Create a requirements.txt
file
Next, create a requirements.txt
file.
Since we are using PostgreSQL, we need to include a database adapter. Additionally, we need to add Django-environ
to utilize environment variables.
The following list represents the minimum configuration required.
django
psycopg2-binary
django-environ
4. Place environment variables in the settings.py
file
Now, let's modify settings.py
as accompanying code.
The changes include the top part of the page and the DATABASES section.
Other than that, there are no further changes.
At the top of the page, the three lines beginning with env=
are important.
We are creating an instance of Django-environ
and specifying the BASE_DIR
in order to load the env
file.
In this project, we placed .env
in the project root.
However, if you want to put the .env
file in the same folder as settings.py
, replace the BASE_DIR =Path(__file__).resolve().parent.parent
code with the following.
ENV_DIR = Path(__file__).resolve().parent
DEBUG setting
For DEBUG setting, the configuration is as follows.
DEBUG = os.getenv('DEBUG_VALUE') == 'TRUE'
The above expression means that if the value of DEBUG_VALUE
in the .env
file is TRUE
, then DEBUG_VALUE
and the right-hand side of the equivalence operator will match, resulting in the whole expression evaluating to TRUE
.
Conversely, if DEBUG_VALUE
is FALSE
, then the entire expression will evaluate to FALSE
because it does not match the right-hand side of the equivalence operator.
DATABASE setting
The database settings have been drastically changed from the default SQLite configuration. Please pay particular attention to the HOST
field here.
As mentioned earlier, the HOST
value should be the service name of the database specified in the docker-compose.yaml
file. In the .env
file below, we have set DB_HOST
to db_blog
.
If you are using Docker, you may find yourself unsure of the correct value to use for the host field.
Many people have encountered this issue, so please be careful with this part.
from pathlib import Path
import environ
import os
# ↓↓↓↓↓ The part in between has changed. ↓↓↓↓↓
env = environ.Env()
BASE_DIR = Path(__file__).resolve().parent.parent
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
SECRET_KEY = os.getenv('SECRET_KEY')
DEBUG = os.getenv('DEBUG_VALUE') == 'TRUE'
# ↑↑↑↑↑ The part in between has changed. ↑↑↑↑↑
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'blog.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'blog.wsgi.application'
# Database
# ↓↓↓↓↓ The part in between has changed. ↓↓↓↓↓
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': os.getenv('DB_HOST'),
'PORT': os.getenv('DB_PORT')
}
}
# ↑↑↑↑↑ The part in between has changed. ↑↑↑↑↑
# Password validation
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
.env file
Here is the .env
file that needs to be applied to the environment variable in settings.py
:
As previously mentioned, DB_HOST
is the database service name set in Docker. Additionally, the port has been set to DB_PORT=5432
. In this example, 5432
is the port on the Docker container side, as configured in docker-compose.yaml
. 5451
is the port on the local computer side.
When specifying the PostgreSQL port in settings.py
, make sure to use 5432
, not 5451
. This means that you should specify the port on the Docker container side.
DEBUG_VALUE=TRUE
SECRET_KEY=****************************************************************
DB_NAME=postgres
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=db_blog
DB_PORT=5432
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
.gitignore file
At this point, let's create a .gitignore
, taking into account that we will be using github for development.
You can find plenty of gitignore for using django by doing a search. The .gitignore I use is pushed here.
At this point, the file structure should look something like this.
5. Build the Dockerfile
Now that we have completed the preparation steps, it's time to build the Dockerfile
. Enter the following command from the command line, starting from the root folder of your project.
$ docker-compose up --build
6. Confirm Building Django
We have finally reached this point. Let's type the command as accompanying box:
$ python manage.py runserver
If you receive the message as accompanying box, you have succeeded.
System check identified no issues (0 silenced).
February 03, 2022 - 08:02:05
Django version 3.2.10, using settings 'blog.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.
However, if you enter the following URL: http://0.0.0.0:8000/
, the Django screen will not appear. Please change it to http://127.0.0.1:8000/
or http://localhost:8000/
.
After doing so, you should see a screen that looks like this:
7. Make the manage.py
is available
Once you have confirmed that Django is working properly, you will typically need to use the manage.py
command for Django projects to perform tasks such as migration and createsuperuser
.
If you are using Docker, running the command from the local command line will not work. Instead, you will need to enter the Docker container and run the command from there. To do this, use the command as shown in the accompanying box:
If you see a #
on the command line, that means you have succeeded.
This step is also where it is easy to run into issues when building a Django environment using Docker.
Furthermore, this article will not cover database migration or createsuperuser
, as these topics are already covered in other blogs that discuss Django.
docker-compose exec backend sh
8. Adding template, static folder, and STATICFILES_DIRS
The minimum setup is now complete. However, more work is needed to turn it into an application.
To configure the template and static folder settings, create a very simple application that only displays "hello world" in the template.
The next steps are as follows:
- 8-1. Create a new application in the project.
- 8-2. Create a template folder in the root directory and add a template such as `base.html`.
- 8-3. Create a static folder in the root directory to add CSS and js files.
Let's walk through the details of these steps.
8-1. Creating a New Application in the Project
To create a new application, follow these steps:
- Enter Docker using the commands introduced in section 7.
- Type the following command:
python manage.py startapp app
This will add an application named app. The file structure after executing the command will look like this:
8-2. Create a template folder in the root directory and add a base.html template and a home.html
template
After creating the application, the next step is to add it to the Django project.
In settings.py
, add this application as accompanying code.
For reference, if the app name were "blogging", this part would be as follows.
'blogging.apps.BloggingConfig'
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app.apps.AppConfig' #add
]
Once you have reached this point, create a templates
folder. Inside the templates
folder, create a base.html
file and an app
folder. Inside the app
folder, create a home.html
file. Note that the name of the app
folder depends on the name of the application created in the project.
Specifically, the structure inside the templates
folder should look like this:
To display something in the browser, you also need to add code to urls.py
and views.py
, which will be explained later.
Next, create a static
folder in the project's root directory. Inside the static
folder, create a css
folder and a js
folder. Inside these folders, create empty custom-style.css
and custom.js
files.
templates/
├── base.html
└── app/
└── home.html
To reflect the code written in CSS or JS in the template, you need to add STATICFILES_DIRS
to settings.py
. Add the accompanying code under STATIC_URL = '/static/'
in settings.py
:
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
9. Add minimal code to urls.py
and views.py
to display hello world.
As previously mentioned, we will now write the minimum amount of code to display "hello world" in the browser.
First, add the accompanying code to urls.py
located in the project folder.
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('app.urls', namespace='app')),
]
Next, let's write the view. In this case, we will use TemplateView
because we believe it provides a more straightforward, minimal setup.
from django.views.generic import TemplateView
class HomeView(TemplateView):
template_name = "app/home.html"
Next, create a urls.py
file in the app
folder and include the accompanying code:
from django.urls import path
from app import views
app_name = "app"
urlpatterns = [
path('', views.HomeView.as_view(), name="home"),
]
Now, you are ready to add code to the TEMPLATE.
To keep the code clean, do not write the actual content in base.html
when displaying something in the browser. Instead, write the actual content in home.html
.
In this example, we will display "Hello world". Specifically, add the accompanying code.
{% load static %}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="{% static 'css/custom-style-base.css' %}">
<title>Document</title>
</head>
<body>
{% block content %}
{% endblock %}
<script src="{% static 'js/custom.js' %}"></script>
</body>
</html>
{% extends 'base.html' %}
{% block content %}
<h1>Hello world!</h1>
{% endblock %}
The message as accompanying image should now appear in your browser:
Congratulations! You have completed a basic Django setup. We can now use this setup as a base from which to experiment with different applications.
Avoid Using the Same Port
In the final section, I will discuss a common problem that arises when building a development environment with Docker. If the local port overlaps with the port used by another Docker container, the container will not start properly.
To avoid this issue, you need to set a non-overlapping port number or stop the container with the overlapping port. You can stop the container using the command line, but this method has been described in many other articles on the web. Since I am using Windows 11 as my local OS, I will show you how to stop it from the Docker desktop in Windows 11.
You can see the port number written in the app section of each container after launching Docker for Windows and selecting containers/Apps, as shown in the image below. If any container uses the same port as the one specified in the docker-compose.yaml file, stop it by selecting the stop button from the menu on the right.
That's all. This basic setup will be used as a premise in other articles on this blog. It is also helpful for quick environment creation in everyday app development. Please make use of it!
Lastly, the GitHub repository for this project can be found here.