Make a Django project production-ready, create a Docker Image and use GitHub CI/CD to automate the push of the Docker image
DoriDoro
Posted on August 1, 2024
Introduction:
In this article, we will walk through a detailed explanation of
- Making Your Django Project Production-Ready
- Create a Dockerfile
- Build, check, run, tag and push the Docker image
- Automating CI/CD with GitHub Actions for a Docker-Based Django Application
for building a containerized Django web application.
Dockerfile for a Django project (Django version 5.0), which is saved on GitHub and will be deployed on Render with help of a CI/CD pipeline of GitHub actions.
Making Your Django Project Production-Ready with WhiteNoise and Gunicorn
When it comes to deploying a Django application in a production environment, two important tools can significantly improve your setup: WhiteNoise and Gunicorn. Both play crucial roles in ensuring that your Django application runs efficiently and securely. This article will delve into what these tools are, their purposes, and why they are essential for a production-ready Django project.
WhiteNoise: Serving Static Files Efficiently
WhiteNoise is a Python package for serving static files directly from your web application. While Django has built-in static file handling capabilities, they are primarily geared toward development and are not suitable for production environments. Here’s why WhiteNoise is a valuable addition:
Ease of Use: WhiteNoise is straightforward to set up and integrates seamlessly with Django. It allows you to serve static files without requiring an external web server like Nginx or Apache.
Performance: WhiteNoise achieves efficient file serving by using a robust static file caching mechanism. This means it can distribute static files with the appropriate caching headers, ensuring that the files are stored in client-side caches, reducing load times and server strain.
Security: WhiteNoise provides sensible security defaults. It handles Content Security Policy (CSP) headers and ensures that cache-busting works correctly, which is crucial for deploying updates reliably.
Compression: It also comes with out-of-the-box support for Gzip and Brotli compression. These compression techniques reduce the size of the static files sent to clients, resulting in faster load times.
Why WhiteNoise?
- Simplifies Deployment: You don’t need to set up and manage an additional server or CDN for serving static files.
- Improves Performance: Efficient caching and compression mechanisms enhance the overall performance of your web application.
- Enhances Security: Security features and sensible defaults help in serving static files securely.
Gunicorn: Efficient Application Server
Gunicorn, short for "Green Unicorn", is a Python HTTP server for WSGI applications. It is a pre-fork worker model which allows handling many requests in parallel. Here’s why Gunicorn is indispensable for running Django applications in production:
Speed and Efficiency: Gunicorn is designed to be fast and lightweight, making it an excellent choice for performance-critical applications. It efficiently handles incoming HTTP requests and delegates them to the appropriate application code.
Scalability: With its ability to run multiple worker processes, Gunicorn can scale to handle a large number of concurrent requests. You can adjust the number of workers and threads according to your application's load.
Compatibility: Gunicorn is WSGI-compliant, meaning it can work with any WSGI-compatible web framework, including Django. This makes it a versatile choice for various Python web applications.
Simplicity and Flexibility: It comes with a simple command-line interface and a plethora of configuration options, allowing you to tailor it to your specific needs effortlessly.
Why Gunicorn?
- Robust Request Handling: An optimized request handling mechanism ensures that your application can handle high traffic efficiently.
- Concurrency: Multi-threading and multi-processing capabilities allow your application to handle many users concurrently without performance degradation.
- Flexibility: Easily configurable to meet the different requirements of various deployments and environments.
Preparing Django for Production with WhiteNoise and Gunicorn
Integrating WhiteNoise and Gunicorn into your Django project ensures a smoother transition from development to production. Here's how:
Step-by-Step Implementation:
-
Install WhiteNoise and Gunicorn:
pip install whitenoise gunicorn
-
Configure WhiteNoise in Django:
In yoursettings.py
file, add the following configurations:
# settings.py MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", # add the whitenoise middleware here "whitenoise.middleware.WhiteNoiseMiddleware", ] STORAGES = { # ... "staticfiles": { "BACKEND": "whitenoise.storage.CompressedStaticFilesStorage", }, }
-
Collect static files:
Command in terminal to collect all static files:
python manage.py collectstatic
-
Use Gunicorn to Run Your Application:
Command in terminal to use Gunicorn as the application server:
gunicorn my_project.wsgi:application --bind 0.0.0.0:8000
Conclusion
Using WhiteNoise and Gunicorn greatly improves the efficiency, performance, and security of your Django application in a production environment. While WhiteNoise simplifies and secures static file serving, Gunicorn ensures your application can handle large volumes of traffic with minimal latency. Together, they provide a robust foundation for deploying a Django application that is ready to meet the demands of real-world usage.
Create a Dockerfile:
In this part of the article, a detailed explanation of a Dockerfile used for building a containerized Django web application is displayed. This Dockerfile is tailored for a Django application, but the principles can be applied to other frameworks and types of applications. The Dockerfile has to be created in the root directory of your Django project.
# Dockerfile
# Use the official Python image from Docker Hub as the base image
FROM python:3.11-slim
# Set the working directory in the container
WORKDIR /app
# Environment variables to prevent Python from writing .pyc files and to buffer stdout/stderr
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Copy the requirements.txt file into the container
COPY ./requirements.txt .
# Install the Python dependencies
RUN pip install --upgrade pip && pip install -r requirements.txt
# Copy the rest of the application code into the container
COPY . .
# Collect static files for the Django application
RUN python manage.py collectstatic --noinput
# Expose port 8000 to allow traffic to the application
EXPOSE 8000
# Define the command to run the application
CMD ["gunicorn", "portfolio.wsgi:application", "--bind", "0.0.0.0:8000"]
Dockerfile Breakdown
FROM python:3.11-slim
-
Base Image: The
python:3.11-slim
image is a minimal Python environment tailored to run lightweight applications. It's based on Debian with only the essential packages, making it a good choice for maintaining a small image size.
WORKDIR /app
-
Setting Working Directory: The
WORKDIR
instruction sets the working directory inside the container to/app
. Any subsequent instructions will be executed in this directory.
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
-
Environment Variables:
-
PYTHONDONTWRITEBYTECODE=1
: Prevents Python from writing.pyc
files to disk, which can save space and reduce I/O operations. -
PYTHONUNBUFFERED=1
: Ensures that the Python output is sent straight to the terminal (or log) without being buffered, which is useful for debugging and real-time logging.
-
COPY ./requirements.txt .
-
Copying Dependencies File: This instruction copies the
requirements.txt
file from the host machine to the current working directory inside the container (/app
).
RUN pip install --upgrade pip && pip install -r requirements.txt
-
Installing Dependencies:
-
pip install --upgrade pip
: Updatespip
to the latest version. -
pip install -r requirements.txt
: Installs the dependencies listed inrequirements.txt
.
-
COPY . .
- Copying Application Code: Copies all remaining files from the current directory on the host machine to the working directory in the container.
RUN python manage.py collectstatic --noinput
-
Collecting Static Files: The
collectstatic
command (specific to Django) moves all static files (CSS, JavaScript, images) into a single location for easy serving. The--noinput
flag makes sure the command runs non-interactively.
EXPOSE 8000
- Exposing Ports: Informs Docker that the container listens on port 8000. This makes it possible to map the container port to a port on the host machine for network access.
CMD ["gunicorn", "portfolio.wsgi:application", "--bind", "0.0.0.0:8000"]
-
Starting the Application: Defines the default command to run when the container starts. It uses
gunicorn
(a Python WSGI HTTP Server) to serve the Django application.-
"portfolio.wsgi:application"
: Specifies the WSGI application callable to use. -
"--bind", "0.0.0.0:8000"
: Bindsgunicorn
to all network interfaces on port 8000.
-
Conclusion
This Dockerfile encapsulates the necessary steps to containerize a Python web application efficiently. By leveraging Docker, one can ensure application consistency across different environments, simplify dependencies management, and streamline deployment processes. Below are a few key takeaways from this Dockerfile:
- Lean Base Image: Using a slim version of the Python image keeps the container lightweight.
- Working Directory: Structuring the container’s file system for clarity and organization.
- Dependency Management: Ensuring all necessary packages are installed.
- Environment Variables: Optimizing Python behavior for containerized environments.
- Static Files Handling: Preparing static assets for production environments.
- Port Exposure: Making the application accessible via a specific port.
- Command Specification: Defining the service entry point cleanly and efficiently.
Build, check, run, tag and push the Docker image:
Build the Docker image:
docker build -t portfolio .
portfolio is the name of the image, you can chose it however you like.
Verify/check the image(s) on your machine:
docker images
Start/run the container of the local Docker image:
docker run -p 8000:8000 -e SECRET_KEY=secret -e ALLOWED_HOSTS='*' -e DEBUG=True portfolio
[2024-07-18 16:05:26 +0000] [1] [INFO] Starting gunicorn 22.0.0
[2024-07-18 16:05:26 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
[2024-07-18 16:05:26 +0000] [1] [INFO] Using worker: sync
[2024-07-18 16:05:26 +0000] [7] [INFO] Booting worker with pid: 7
go to url: http://0.0.0.0:8000 in your browser and you should see your website.
My website example looks like this:
Verify running container:
docker ps
Verify all container, even already stopped:
docker ps -a
-a --all (displays all container)
Removes all stopped/unused containers:
docker container prune -f
-f (--force)
If your local Docker image is working fine then the next steps are:
Tag your local image with the Docker Hub repository name:
docker tag portfolio your_username/portfolio:latest
portfolio: is the name of the local repository
your_username/portfolio: your_username is your username of Docker Hub. and "/portfolio" is the name of your Docker Hub repository.
Login into Docker Hub:
docker login -u your_username
Push the tagged image to Docker Hub:
docker push your_username/portfolio:latest
latest: is the tagname, the tag version
Automating CI/CD with GitHub Actions for a Docker-Based Django Application
In the ever-evolving world of software development, Continuous Integration and Continuous Delivery (CI/CD) are pivotal for ensuring rapid, reliable, and resilient application development and deployment processes. GitHub Actions provides an awesome platform to automate these workflows. This article will break down a GitHub Actions configuration file designed to build and push Docker images to Docker Hub whenever changes are pushed to the main
branch or pull requests are made against it.
Preparation:
Setting up secrets on GitHub is a straightforward process and is essential for securely storing sensitive information like API keys, tokens, or other credentials.
- Navigate to your repository on GitHub.
- Click on the "Settings" tab.
- In the left sidebar, click on "Secrets and variables" > "Actions".
- Click the "New repository secret" button.
- Add the SECRET_KEY secret:
- Name: SECRET_KEY
- Value: (Your secret key value)
- Click "Add secret".
Repeat the steps to add ALLOWED_HOSTS, DEBUG, DOCKERHUB_TOKEN and USERNAME secrets. (If you have different usernames for Docker Hub and GitHub, then create several secrets)
Overview of the GitHub Actions Workflow
Let's take a look at the provided config.yml
file, which is located in .github/workflows/
directory:
# .github/workflows/config.yml
name: Continuous Integration and Delivery
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
docker_actions:
name: Docker
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: your_username/portfolio:latest
Breakdown of the Workflow
1. Workflow Name and Triggers
name: Continuous Integration and Delivery
on:
push:
branches:
- main
pull_request:
branches:
- main
Workflow Name: This is set to "Continuous Integration and Delivery".
-
Triggers: The workflow triggers on two events:
-
push
to themain
branch. -
pull_request
events targeting themain
branch.
-
This ensures that the workflow runs whenever code is pushed to the main branch or when a pull request is created or updated against the main branch.
2. Job Definition
jobs:
docker_actions:
name: Docker
runs-on: ubuntu-latest
-
Job Name:
docker_actions
, with a display name ofDocker
. -
Runs-On: The workflow runs on the latest version of Ubuntu (
ubuntu-latest
), ensuring a consistent and up-to-date environment.
3. Steps of the Job
The job comprises a series of steps crucial for setting up the Docker environment, building, and pushing the Docker image.
Step 1: Set up QEMU
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- QEMU Setup: This action sets up QEMU, a generic and open-source machine emulator and virtualizer. It's essential for building multi-platform Docker images.
Step 2: Set up Docker Buildx
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- Buildx Setup: Docker Buildx is a CLI plugin that extends Docker with the full support of the features provided by Moby BuildKit builder toolkit. This step is essential for building multi-architecture images and leveraging advanced features such as caching.
Step 3: Login to Docker Hub
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
Docker Hub Login: This step uses the
docker/login-action
to log into Docker Hub using credentials stored in GitHub Secrets.secrets.USERNAME
andsecrets.DOCKERHUB_TOKEN
are environment variables that securely store your Docker Hub username and access token, respectively.
Step 4: Build and Push Docker Image
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: your_username/portfolio:latest
-
Build and Push:
-
docker/build-push-action
is used to build and push Docker images. -
push: true
indicates that the image should be pushed to Docker Hub if the build is successful. -
tags
specifies the name and tag of the Docker image to be pushed, in this case,your_username/portfolio:latest
.
-
Conclusion
This GitHub Actions configuration automates the CI/CD pipeline for a Django project containerized with Docker. The process ensures that the Docker image is built and pushed to Docker Hub every time code is pushed or updated on the main
branch, making sure that your deployments are up-to-date and reliable.
- Automated Builds: You no longer need to manually build and push Docker images, ensuring consistency and saving time.
- Secure and Efficient: Using encrypted secrets for Docker Hub credentials ensures security, while automated workflows enhance efficiency and reliability.
- Multi-platform Support: The setup steps including QEMU and Buildx ensure that you can build images for multiple architectures if necessary.
By integrating this workflow into your project, you bring the power of continuous integration and delivery to your development pipeline, fostering a healthier, faster, and more reliable deployment cycle.
Resources:
Posted on August 1, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.