Deploying Django Apps with Docker: A Step-by-Step Guide | Better Stack Community

A brief tutorial to configure a django project over docker (without docker compose)

Django, a powerful web framework for Python, has gained immense popularity for its ability to help develop robust web applications quickly. Once your Django application is ready for deployment, it's essential to have a seamless and efficient deployment process that guarantees consistency across different environments. This is where Docker, a versatile containerization platform, comes into play.

Docker offers a convenient way to package your Django application with its dependencies and configurations into a self-contained unit called a container. These containers are lightweight, portable, and isolated from the underlying system, making them ideal for deploying applications consistently across different environments.

By Dockerizing your application, you'll unlock several compelling benefits:

  • Simplified dependency management that ensures consistent software and library versions across different environments, eliminating the "works on my machine" problem.
  • Isolated environments that guarantee consistent behavior regardless of the underlying host system, making deployment and maintenance easier.
  • Built-in scalability and management capabilities that empowers your application to handle increased traffic and workload demands by leveraging Docker's container orchestration tools, such as Docker Swarm or Kubernetes.
  • Enhanced portability that enables you to develop and test your applications locally, and deploy them on any Docker-supported platform.

This tutorial will walk you through containerizing your Django application with Docker. You'll start by cloning an existing Django application from a GitHub repository that performs CRUD operations with a PostgreSQL database. Then you'll create a Dockerfile and build a Docker image locally to ensure it functions correctly. You'll also learn to share the Django container image using a public container registry like DockerHub.

Additionally, you will see the steps involved in deploying the application with Docker compose. By the end of this tutorial, you will be able to deploy your Django applications using Docker confidently.

Side note: Get a Python logs dashboard

Save hours of sifting through Python logs. Centralize with Better Stack and start visualizing your log data in minutes.

See the Python demo dashboard live.

Prerequisites

To ensure a smooth understanding and implementation of the tutorial, ensure that:

  • You have a basic understanding and familiarity with Python and Django concepts.
  • Python3 and Pip must be installed on your computer.
  • You have basic command-line skills.
  • PostgreSQL is installed and running on your machine.
  • You've signed up for a free DockerHub and GitHub account.

After ensuring you've met the above prerequisites, you can proceed with the rest of this tutorial. Note that all the commands present in this article were tested on a fresh Ubuntu 22.04 machine.

Step 1 — Installing Docker locally

If you don't have Docker installed already, follow the relevant link below to get the Docker Engine up and running on your machine:

Once Docker is installed, we recommend that you configure it such that you don't need to prefix the docker command with sudo. The official Docker documentation provides detailed instructions on how to configure Docker without sudo access for Linux and macOS.

To verify that Docker is successfully installed and running on your machine, execute the commands below in the terminal:

Output

Docker version 24.0.5, build ced0996
sudo systemctl status docker

Output

● docker.service - Docker Application Container Engine
     Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: disabled)
    Drop-In: /usr/lib/systemd/system/service.d
             └─10-timeout-abort.conf

Active: active (running) since Tue 2023-06-27 07:54:14 CAT; 3 days ago

TriggeredBy: ● docker.socket Docs: https://docs.docker.com Main PID: 23146 (dockerd) Tasks: 23 Memory: 92.0M CPU: 10min 15.078s CGroup: /system.slice/docker.service └─23146 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

The above output confirms that Docker is ready for use, so you can proceed to the next step where you'll create a PostgreSQL database for your Django application.

Step 2 — Setting up a local PostgreSQL database

In this section, you will create a database for the Django todo application that we'll be containerizing later. Assuming PostgreSQL is already installed on your machine, execute the command below to access the psql CLI with the default postgres user:

sudo -u postgres psql

In the psql interface, create a new user/password combination as follows:

CREATE USER <username> WITH PASSWORD '<password>';

Output

Next, create a new database for the todo application and set the new user that you created as the owner:

CREATE DATABASE django_todo OWNER <username>;

Output

You can now quit the current session with \q, then run the following command to log in once again with your newly created user. Ensure to enter your password if prompted:

psql -U <username> -d django_todo -W

If you're able to login to psql successfully, you may proceed to the next step where you'll set up the todo application on your computer.

Step 3 - Setting up the demo project

In this section, you will set up a simple Django todo application on your machine and run it locally so that you'll confirm that it works as expected before proceeding to Dockerize it.

Start by forking the demo project to your GitHub account, then clone the resulting repository to your computer through the command below:

git clone https://github.com/<username>/django-todo-app

Once the Django project is cloned successfully, navigate into the project directory and view the top-level structure using the ls command:

The project structure should appear as shown below:

Output

django_project  manage.py  requirements.txt  todo_app  venv

Here's a brief explanation of what each entry comprises:

  • django_project: This is the main directory of the Django project. It contains the core files and configurations related to the project.
  • todo_app: This is the directory that contains the files for the todo application.
  • venv: This directory represents a Python virtual environment. It allows you to install project-specific dependencies and isolate them from the system-wide Python installation.
  • manage.py: This file is a command-line utility provided by Django. It allows you to perform various tasks related to the project, such as running the development server, applying database migrations, or executing custom management commands.
  • requirements.txt: This file contains the dependencies used in the project. It lists the names and versions of the Python packages or modules that the project depends on. Installing dependencies using this file ensures that the application environment is easily reproducible on different machines.

In summary, the Django project contains a todo application that performs basic CRUD operations: create, read, update, and delete on a list of todo items.

The next step is to create a .env file at the root of your project and fill it with your PostgreSQL credentials. Go ahead and the following code to the file and ensure to replace the placeholders:

.env

DATABASE_NAME=django_todo
DATABASE_USER=<postgresql_username>
DATABASE_PASSWORD=<postgresql_password>
DATABASE_HOST=localhost
DATABASE_PORT=5432

Now, return to your terminal and activate the Python virtual environment with the following command:

source venv/bin/activate

This should change the prompt in your shell to include (venv).

You may now install the dependencies for the project by executing the following command:

pip3 install -r requirements.txt

If you encounter an error while building the psycopg2, you may install the following packages, then retry the command once again:

sudo apt install libpq-dev python3-dev

After installing the project dependencies, run the database migrations for the application with the following command:

python3 manage.py migrate

Output

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, todo_app
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
  Applying todo_app.0001_initial... OK

You are now set to launch the application server at this stage. Go ahead and run the command below to set it up:

python3 manage.py runserver

Output

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
June 08, 2023 - 19:36:49
Django version 4.2.1, using settings 'django_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Navigate to http://localhost:8000/ in your preferred web browser to view the todo_app application in action:

To confirm that everything works, create a todo item by clicking on the Add Todo button at the top left corner of the homepage. You will be redirected to the /create route, where you can input the title of your item, a description, a due date, and check whether the item has been completed or not.

Once you click the Save button, you should see the newly added item on the homepage:

Now, return to your psql interface and execute the following sql query below:

SELECT * FROM todo_app_todo;

You should see your recently entered todo item as follows, confirming that everything is working as expected:

Output

 id |    title     |                     description                     |  due_date  | completed
----+--------------+-----------------------------------------------------+------------+-----------
 1  | Learn French | Download the Duolingo App and begin my French class | 2023-07-20 | f
(1 row)

At this point, you may quit both the development server and the psql interface database by pressing CTRL-C and typing \q respectively in the terminal.

Now that you've successfully set up your Django application locally, let's move on to the next step where you'll create a Dockerfile for your project.

Step 4 — Creating a Dockerfile for your project

In this section, you will create a Dockerfile that defines instructions for building a Docker image for your project. You'll specify the base image, copy the application code, install dependencies, configure the container environment, and finally specify the command to launch the application server.

Create a Dockerfile in the root of your project directory as follows:

Paste the following code into the file and save it:

django-todo-app/Dockerfile

FROM python:3.11

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

Each line in the Dockerfile above represents a specific instruction for building the Docker image, and the instructions are executed from top to bottom. Let's go through each line of the Dockerfile to understand its purpose:

  • FROM python:3.11: This line specifies the base image for our application's Docker image. In this case, it's the Python 3.11 image, which contains the essential OS files and dependencies for running Python applications. All subsequent instructions in this file will be committed on top of this base image.
  • WORKDIR /app: Sets the working directory inside the Docker container to /app. This directory is where subsequent commands will be executed within the container. Note that this directory is created if it doesn't already exist.
  • COPY requirements.txt .: Copies the requirements.txt file from the project directory on your machine to the current working directory inside the Docker container (/app).
  • RUN pip install --no-cache-dir -r requirements.txt: This line installs all the dependencies listed in requirements.txt using the pip install command. The --no-cache-dir flag prevents pip from using its cache during the installation process.
  • COPY . .: Copies the entire project directory from the host machine to the /app directory in the Docker image.
  • EXPOSE 8000: Informs Docker that the application will listen on port 8000. However, it doesn't publish the port to the host machine.
  • CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]: Finally, this instruction specifies the default command to run when the Docker container is launched. In this case, the application server listens on all available network interfaces on port 8000.

The Dockerfile above sets up a container environment for running your Django application. It uses Python 3.11 as the base image, installs the project dependencies, copies the application code into the image, exposes port 8000, and specifies the command to run a container based on this Docker image is launched. These instructions provide a way to run the Django application in an isolated and reproducible environment using Docker.

Now that you've specified all the necessary instructions for building the Docker image, you can build the image in the next step.

Step 5 — Building the Docker image

In this section, you will use the Dockerfile created in the previous section to build the Docker image for your Django project. Before you run the docker build command to build the image, create a .dockerignore file at the root of your project:

Add the following line to the file:

django-todo-app/.dockerignore

The .dockerignore file describes files and directories you want to exclude when building a Docker image. It follows a roughly similar (but not identical) syntax to .gitignore files. You can learn more about its syntax here.

The above .dockerignore file ignores the .env file that contains the application secrets so that it's not copied into the Docker image. This is important so your application secrets are not exposed in the Docker image. In a subsequent section, we will discuss the proper way to pass a .env file to the Docker container. Now, go ahead and execute the command below to build the image:

docker build -t django-todo-app .

Docker will use the current directory as the build context to create the image based on the instructions in the Dockerfile. The image is also tagged django-todo-app, so it's easy to identify amongst your other Docker images.

Once the image is built successfully, you should see the following output:

Output

[+] Building 245.1s (11/11) FINISHED
...

Go ahead and verify the existence of the Docker image by running the command below:

You should see the newly built image in the output:

Output

REPOSITORY        TAG       IMAGE ID       CREATED          SIZE
django-todo-app   latest    a38a1effee6c   27 seconds ago   1.11GB

In the next step, we will turn our attention to building a Docker image that will run the PostgreSQL server.

Step 6 — Building a PostgreSQL image

In this section, you'll run a PostgreSQL container image and establish a network connection between the todo app container and the PostgreSQL container. This will ensure your Django application works properly on your local machine before deploying it to production.

First, deactivate your virtual environment and stop the local PostgreSQL server by typing the following commands:

sudo systemctl stop postgresql

Next, create a Docker network that will facilitate communication between the Django application container and the PostgreSQL container using the command below. It will create a network named my-django-postgres-network to connect the containers.

docker network create my-django-postgres-network

Output

c1f819a2b9868e25148167be8418875a4f2e8fd9192a2f668829fee565e2ed10

The next step is to build and run a Docker container for the PostgreSQL database using the official PostgreSQL image and connect it to the network you just created:

docker run --name my-postgres -p 5432:5432 -e POSTGRES_USER=<your_postgres_user> -e POSTGRES_PASSWORD=<your_posgres_user_password> --network my-django-postgres-network -d postgres

Replace the placeholder variables in the above command with the DATABASE_USER and DATABASE_PASSWORD variables used in your .env file. This will create a PostgreSQL container with a user with the specified username and password during initialization.

Once the PostgreSQL container is created and running, you should see the following output:

Output

...
4ae692d11ad3: Pull complete
Digest: sha256:31c9342603866f29206a06b77c8fed48b3c3f70d710a4be4e8216b134f92d0df
Status: Downloaded newer image for postgres:latest
d7289abba02eba999e9449125653c34936c7dd19337aa7a3901f23c0a8fbf52c

You can confirm that the PostgreSQL container is up through the command below:

The STATUS field should report "Up" followed by the uptime:

Output

CONTAINER ID   IMAGE             COMMAND                  CREATED         STATUS         PORTS                                       NAMES
a5c1ac476e54   postgres          "docker-entrypoint.s…"   4 minutes ago   Up 4 minutes   0.0.0.0:5432->5432/tcp, :::5432->5432/tcp   my-postgres

The next step is to start an interactive bash session within the PostgreSQL container so that you can set up your database:

docker exec -it my-postgres bash

You should see a new bash prompt with the root user like this:

Output

root@1ea83503fc9b:/#

Enter the following command to launch the psql interface:

psql -U <database_user>

Next, create the database for your Django application using the same name as before:

CREATE DATABASE django_todo OWNER <database_user>;

Output

You may now exit the psql interface by typing \q, then exit the bash shell by typing exit. At this point, your PostgreSQL database has been set up successfully within the container so you may proceed to the next section where you'll run the Django application container and connect it to the PostgreSQL container.

Step 7 — Running the Django application container

In this section, you'll start a new container for your Django application based on the django-todo-app image built in Step 5. Before you do that though, update the DATABASE_HOST value in your .env file as follows:

django-todo-app/.env

. . .
DATABASE_HOST=my-postgres
. . .

This change allows your Django application container (once created) to connect to the PostgreSQL container created in step 6 above. The value of DATABASE_HOST must correspond with the name of the PostgreSQL container, which in this case is my-postgres.

You're now all set to run the Docker container for your Django application. Execute the command below to start the container, specify its network, and link it to the PostgreSQL container:

docker --env-file .env run --name django-todo-app-c1 -p 8000:8000 --network my-django-postgres-network -d django-todo-app

This command will create a container named django-todo-app-c1 for your Django application and map port 8000 from the container to port 8000 on your local machine. Notice how the .env file is fed into the container via the --env-file option so that the environmental variables are applied properly before the application launches.

Once the container is running, you should see the following output:

Output

40b68935ef40f0c5a5e4206bd82ed4171ba9620d85edde7b91174b1b0c97ef2e

Confirm that both the PostgreSQL and your Django application containers are running by executing the following command:

If both containers are up and running, you should see the following output:

Output

CONTAINER ID   IMAGE             COMMAND                  CREATED          STATUS          PORTS                                       NAMES
bf68ef492145   django-todo-app   "python manage.py ru…"   22 seconds ago   Up 22 seconds   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   django-todo-app-c1
3ce92be1091c   postgres          "docker-entrypoint.s…"   3 minutes ago    Up 3 minutes    0.0.0.0:5432->5432/tcp, :::5432->5432/tcp   my-postgres

Next, apply the migrations once again using the following command:

docker exec -it django-todo-app-c1 python manage.py migrate

Output

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, todo_app
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
  Applying todo_app.0001_initial... OK

Now, visit the following address in your preferred web browser to view your Django todo_app application once again:

http://127.0.0.1:8000/

You should be able to access your Django application running inside the Docker container:

You can also try to add a new todo item to very that the connection to the PostgreSQL container also works perfectly:

Now, execute the following commands to stop and remove the running PostgreSQL and Django app containers since you have verified that the application works as expected:

docker rm my-postgres --force
docker rm django-todo-app-c1 --force

In the next section, you will learn how to share the Docker images you've just built so that others can download them and run them on their machines.

Step 8 — Sharing the Docker image with a different user or machine

In this step, you will explore sharing the Docker image with a different user or machine. Sharing the image allows others to access and deploy your application in their environments reproducibly. You'll also see how to push the image to a public Docker registry called Docker Hub, a cloud service for storing and distributing Docker images.

Start by logging into your Docker Hub account on your host machine with the following command, you can skip this process if you are already logged in:

This command will prompt you to input your Docker Hub username and password. Once you have inputted these values, you should see the following output if authenticated successfully:

Output

Tag the image with your Docker Hub username and repository name with the following command. Doing this will provide a standardized way of identifying and managing your images on Docker Hub.

docker tag django-todo-app <dockerhub_username>/django-todo-app

Next, execute the command below to push the image to your Docker Hub repository. It uploads the entire Docker image to your DockerHub account so that you can download it on other machines or make it accessible to other team members.

docker push <dockerhub_username>/django-todo-app

Output

...
b4b4f5c5ff9f: Mounted from library/python
b0df24a95c80: Mounted from library/python
974e52a24adf: Mounted from library/python
latest: digest: sha256:ceb417f8f45e4ffe9724e052f0bc29d8f596dfdf923365b93288cb30b4ec5ef4 size: 2844

Once you head over to your Docker Hub repository, you should see the new entry for your Django application as follows:

At this point, you can download the image onto a different machine using the docker pull command as follows:

docker pull <dockerhub_username>/django-todo-app

This will download the Docker image accordingly so that you may run containers based on that image.

Step 9 - Automating the building and sharing process with GitHub actions

In this step, you will streamline the process of building and sharing the Docker image, using a continuous integration and continuous deployment (CI/CD) pipeline made with GitHub actions. This will ensure that whenever you push a commit to your repository, a Docker image will be automatically built and uploaded to Docker Hub without any further action on your part.

Ensure that you're at your project root before running the command below to create the .github/workflows directory:

mkdir -p .github/workflows

Next, create the following YAML file within that directory:

code .github/workflows/docker.yml

Paste the following code into the file, and ensure to replace the placeholders:

.github/workflows/docker.yml

name: Build and share Docker Image to Docker Hub

on:
  push:
    branches:
      - main

jobs:
  build-and-share:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO_NAME }}:latest

Notice that file above contains three secrets: DOCKERHUB_USERNAME, DOCKERHUB_REPO_NAME, and DOCKERHUB_TOKEN. The DOCKERHUB_TOKEN is an access token required to pushing the Docker image from GitHub to Docker Hub. You can follow this guide to create the token on your Docker Hub account. Ensure to copy this token and keep it in a safe place.

Next, set up secrets in your GitHub repository to store the values for your DockerHub credentials by following the steps:

  1. Go to your repository on GitHub.
  2. Click on the Settings tab.
  3. In the left sidebar, click on Secrets and Variables then Actions.
  4. Click on the New repository secret button.
  5. Provide a name for your secret, for example, DOCKERHUB_USERNAME.
  6. Enter the value for the secret in the Secret field.
  7. Click the Add secret button to save the secret.

Repeat steps 4-7 for the DOCKERHUB_TOKEN and DOCKERHUB_REPO_NAME secrets.

Once you've saved the secrets to your repository, you can now then stage, commit and push your changes to GitHub:

git commit -m "add Dockerfile and configure github actions"
git push origin main

Navigate to the Actions tab on your GitHub repository to view the status of your workflow.

Considering the image above, you can see that the pipeline ran to completion. If not, wait a little for the pipeline to complete. Afterward, verify that it was successful by viewing your Docker Hub repository.

Step 10 — Deploying the Django application with Docker Compose

Docker Compose is a tool that simplifies the management of multi-container Docker applications. It allows you to orchestrate your Django application stack, including the web server, database, and other services.

Instead of manually creating and managing individual Docker containers using the docker run command, Compose lets you define and manage multi-container applications in a single YAML file. This saves time and provides a structured way to handle complex applications by specifying all the relevant services, configurations, and dependencies.

In this section, you'll set up a Docker Compose configuration for your Django application, and deploy your application accordingly. Start by creating a docker-compose.yml file in the root of your Django project, then paste in the following contents:

code docker-compose.yml

django-todo-app/docker-compose.yml

version: '3.8'

services:
  my-postgres:
    image: postgres:15
    container_name: db
    environment:
     - POSTGRES_DB=${DATABASE_NAME}
     - POSTGRES_USER=${DATABASE_USER}
     - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
    ports:
    - '5432:5432'
    volumes:
    - pg_data:/var/lib/postgresql/data

  web:
    build: .
    container_name: django
    ports:
      - '8000:8000'
    volumes:
      - .:/app
    environment:
      - DJANGO_ENV=${DJANGO_ENV}
      - DATABASE_NAME=${DATABASE_NAME}
      - DATABASE_USER=${DATABASE_USER}
      - DATABASE_PASSWORD=${DATABASE_PASSWORD}
      - DATABASE_HOST=${DATABASE_HOST}
      - DATABASE_PORT=${DATABASE_PORT}
    depends_on:
      - my-postgres

volumes:
  pg_data:

The Docker compose configuration above defines two services: my-postgres and web. It sets up a PostgreSQL database service (my-postgres) and a Django web application service (web), then configures both services to communicate with each other and provide the necessary environment and volume configurations for development.

The next step is to build the images for the services defined in the Docker Compose file using docker compose. Before proceeding, ensure that the Compose plugin is installed. You can verify this by typing:

docker compose version

You should observe the following output:

Output

Docker Compose version v2.20.2

Next, run the command below to build the images defined in the docker-compose.yml file:

docker compose build

Output

my-postgres uses an image, skipping
Building web
[+] Building 75.8s (11/11) FINISHED
...
=> [5/5] COPY . .                                                                                                                                                2.7s
 => exporting to image                                                                                                                                            2.0s
 => => exporting layers                                                                                                                                           2.0s
 => => writing image sha256:b7174aee618ff55c470f30dd118b419527210e1ba18fc3ba42bc2eadf79cb1a8                                                                      0.0s
 => => naming to docker.io/library/django-todo-app_web

Next, stop and remove any existing containers on your machine using the commands below:

docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)

But if there are no running containers, you will see the error message:

Output

$(docker ps -a -q)
"docker stop" requires at least 1 argument.
See 'docker stop --help'.

Usage:  docker stop [OPTIONS] CONTAINER [CONTAINER...]

Stop one or more running containers

Afterward, run the command below to create and launch the defined services. Ensure that it is run in the same directory as your .env file so that the environmental variables specified in docker-compose.yml are substituted accordingly:

Output

Digest: sha256:362a63cb1e864195ea2bc29b5066bdb222bc9a4461bfaff2418f63a06e56bce0
Status: Downloaded newer image for postgres:15
Creating db ... done
Creating django ... done
Attaching to db, django
db             |
db             | PostgreSQL Database directory appears to contain a database; Skipping initialization
db             |
db             | 2023-07-09 09:28:53.430 UTC [1] LOG:  starting PostgreSQL 15.3 (Debian 15.3-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
db             | 2023-07-09 09:28:53.430 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
db             | 2023-07-09 09:28:53.430 UTC [1] LOG:  listening on IPv6 address "::", port 5432
db             | 2023-07-09 09:28:53.503 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db             | 2023-07-09 09:28:53.556 UTC [29] LOG:  database system was shut down at 2023-07-09 09:24:44 UTC
db             | 2023-07-09 09:28:53.575 UTC [1] LOG:  database system is ready to accept connections
django         | Watching for file changes with StatReloader

From the output above, you can see that it creates and starts the containers based on the built images, the sets up the network and volumes specified in the docker-compose.yml configuration file.

In a different terminal, apply database migrations for your Django application using the following command:

docker compose run web python manage.py migrate
[ouput]
Creating django-todo-app-1_web_run ... done
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, todo_app
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
  Applying todo_app.0001_initial... OK

To verify that the application works as expected, visit http://localhost:8000/ in your browser and add a new todo item. It should list the new item once you hit the Save button as before.

Return to your terminal, and create an interactive bash session for the Docker container running your PostgreSQL database with the following command:

docker compose exec my-postgres psql -U <your-postgres-user> -d django_todo

Output

psql (15.3 (Debian 15.3-1.pgdg120+1))
Type "help" for help.

django_todo=#

In the psql interface, use the following query to verify that your todo item exists in the database:

TABLE todo_app_todo;

Output

 id |     title     |                 description                 |  due_date  | completed
----+---------------+---------------------------------------------+------------+-----------
  1 | Buy black tea | I will buy black tea tomorrow at the market | 2023-07-12 | f
(1 row)

With the output above, you've successfully deployed your Django project with Docker Compose!

Final thoughts and next steps

In this article, you learned how to simplify the deployment process for Django applications by containerizing them with Docker and Docker Compose. By encapsulating your Django application, along with its dependencies and runtime environment, within a Docker container, you can achieve consistent deployments across different environments and avoid the common "it works on my machine" problem.

You also discovered how to automate the building and sharing of Docker images through Docker Hub and GitHub Actions. This allows you to easily store and distribute your Docker images, making it convenient for deployment on different machines or sharing with others.

As you continue your journey with Docker and Django, here are some suggested next steps:

The complete code used in this article can be found in this GitHub repository. Thanks for reading, and happy coding!