In this article I will show a working example of running Django and PostgreSQL with Docker Compose
. We’ll have a look at running the Django migrations and creating the superuser as well. Also, we want to keep our database changes so that it remains available if we restart the application.
Let’s dive in. To accomplish this, we need two containers - one for Postgres and one for Django.
Postgres
postgres:
container_name: postgres-container
image: postgres:latest
environment:
POSTGRES_DB: dog_grooming_website
POSTGRES_USER: dog_grooming_user
POSTGRES_PASSWORD: yoursecretpassword
ports:
- "5432:5432"
volumes:
- ./docker/postgres_data:/var/lib/postgresql/data/
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
postgres
: The name of the service.container_name
: The name of the Docker container of this service.image
: The base image which is the latest postgres image. Use the required specific version likepostgres:16
.environment
: Environment variables that define the default Postgres superuser, its password and also the default database.ports
: You can map the ports of the container to the host machine. In case you have your own Postgres running on the host machine, you can map it to another port such as5433:5432
. In this case, the Django container will still reach Postgres on port5432
, but in the host machine it will be available on port5433
.volumes
: You can volume share data between the container and the host machine. In our example we volume share the database data, so even when we start a new container, the data remains available. You could volume share a database init script too as per the following example:./docker/init_db.sql:/docker-entrypoint-initdb.d/init_db.sql
.healthcheck
: You can define a check to see whether the service is healthy or not. The check can be rerun several times based on your parameters. We want that the Django application container is not started until the Postgres container is healthy, because if the database is not up and running, the Django application will fail to connect to the database and execute the migrations.
Django
For the Django application we need an image that has all the requirements installed and has the source code. You can add your source code to the image or you can volume share it. Let’s create our Docker image and based on that the Docker Compose service for our Django application.
Dockerfile:
FROM python:latest
ENV PYTHONUNBUFFERED 1
RUN mkdir /django_app
WORKDIR /django_app
ADD requirements.txt requirements.txt
RUN pip install -r requirements.txt
ADD . /django_app/
As a base we use the official latest python image. You can use a specific Python version too such as python:3.9.6
. Then, we just create a folder for our source code, set it as our working directory, install all the requirements and add our source code to the image.
Including it in Docker Compose:
django-app:
container_name: django-app-container
build:
context: .
dockerfile: docker/Dockerfile
environment:
DB_NAME: dog_grooming_website
DB_TEST_NAME: dog_grooming_website_test
DB_USER: dog_grooming_user
DB_PASSWORD: yoursecretpassword
DB_HOST: postgres
DB_PORT: 5432
DJANGO_SUPERUSER_USERNAME: admin
DJANGO_SUPERUSER_PASSWORD: yoursecretpassword
DJANGO_SUPERUSER_EMAIL: youremail@djangomail.com
ports:
- "8000:8000"
command:
- /bin/sh
- -c
- |
python manage.py migrate
python manage.py createsuperuser --noinput
python manage.py runserver 0.0.0.0:8000
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: curl --fail http://localhost:8000 || exit 1
interval: 10s
timeout: 10s
start_period: 10s
retries: 10
build
: We set the context to the current directory and provide the relative path to the Dockerfile. In my case, I have it inside adocker
folder.environment
: I provide all the database config details as environment variables so that I can set them dynamically in Django’ssettings.py
when running the application. We’ll see it below. I also provide the variables for the Django superuser that we create in the command section. The database host ispostgres
because we named our Postgres service like that. We could put all our environment variables into a separate file and use theenv_file
option instead.ports
: Django runs on port 8000 by default, we use the same and map it to the port 8000 of the host machine.command
: The commands that are executed upon running the container. As you can see, we run the migrations, create the superuser and run the server on port 8000. Creating the superuser is done by considering the environment variables set above.depends_on
: Our Django application depends on thepostgres
service that we defined above. We define a condition that the service has to be healthy, meaning the database up and running.healthcheck
: We add a health check to test whether our Django application is running on port 8000.
Complete Solution
Consider this sample folder structure:
repo
|-- Django source
|-- manage.py
|-- requirements.txt
|-- docker
|-- Dockerfile
|-- .dockerignore
|-- docker-compose.yml
|-- other folder and files
The files and folders you define in the .dockerignore
file will be ignored when adding files into the Docker image. For example:
**/docker/
**/.idea/
**/env/
**/.git/
**/.DS_Store
Dockerfile:
FROM python:latest
ENV PYTHONUNBUFFERED 1
RUN mkdir /django_app
WORKDIR /django_app
ADD requirements.txt requirements.txt
RUN pip install -r requirements.txt
ADD . /django_app/
docker-compose.yml:
version: '1'
services:
postgres:
container_name: postgres-container
image: postgres:latest
environment:
POSTGRES_DB: dog_grooming_website
POSTGRES_USER: dog_grooming_user
POSTGRES_PASSWORD: yoursecretpassword
ports:
- "5432:5432"
volumes:
- ./docker/postgres_data:/var/lib/postgresql/data/
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
django-app:
container_name: django-app-container
build:
context: .
dockerfile: docker/Dockerfile
environment:
DB_NAME: dog_grooming_website
DB_TEST_NAME: dog_grooming_website_test
DB_USER: dog_grooming_user
DB_PASSWORD: yoursecretpassword
DB_HOST: postgres
DB_PORT: 5432
DJANGO_SUPERUSER_USERNAME: admin
DJANGO_SUPERUSER_PASSWORD: yoursecretpassword
DJANGO_SUPERUSER_EMAIL: youremail@djangomail.com
ports:
- "8000:8000"
command:
- /bin/sh
- -c
- |
python manage.py migrate
python manage.py createsuperuser --noinput
python manage.py runserver 0.0.0.0:8000
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: curl --fail http://localhost:8000 || exit 1
interval: 10s
timeout: 10s
start_period: 10s
retries: 10
In Django’s settings.py
we get the database settings from the environment variables if they are available, if not, we load them from a config file (not relevant now). As we provided the environment variables through Docker Compose, they will be used here.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', db_config.get('name', '')),
'USER': os.environ.get('DB_USER', db_config.get('user', '')),
'PASSWORD': os.environ.get('DB_PASSWORD', db_config.get('password', '')),
'HOST': os.environ.get('DB_HOST', db_config.get('host', '')),
'PORT': os.environ.get('DB_PORT', db_config.get('port', '')),
'TEST': {
'NAME': os.environ.get('DB_TEST_NAME', db_config.get('test_db_name', ''))
}
}
}
Example
As an example you can check my latest project on GitHub.
Running The Docker Compose
Just change directory to where you have the docker-compose.yml
and run:
docker-compose up -d
-d
will run it in detached mode.
As we mapped the port 8000 of the Django application container to the same of the host machine, go to 127.0.0.1:8000 in your browser to make sure your application is running.
To stop and remove the containers:
docker-compose down
Conclusion
Docker Compose is a great and easy way to share your projects and make them easy to run. Containerizing applications is a widely used technique in development and production environments as well.
Comments
Post a Comment