Django automated deployments to Digital ocean using GitHub actions

Learn how to create a ci-cd pipeline that deploys a Django application to a digital ocean droplet using GitHub actions.

In this article, you will learn how to build an automated GitHub actions pipeline that deploys a Django application to a digital ocean droplet.

Prerequisites:

  1. A GitHub account.

  2. A docker hub account.

  3. A digital ocean account.

1 Base application.

For this guide, we shall be using a base project that can be cloned here.

Navigate to your desired folder/directory.

cd Desktop

Clone the project

git clone https://github.com/manulangat1/github-actions-do-article.git

Create and activate a virtual environment (use whatever tool you are comfortable with to achieve this.)

python3 -m virtualenv venv 
source venv/bin/activate
#install the required dependancies 
pip install -r requirements.txt

Run the Django development server to confirm that all is well.

python3 manage.py runserver

2. Setting up the GitHub actions file.

On the root of the project create a folder `.github`, and inside it create another folder named workflows and a file inside the workflows folder named integrations.yaml. The structure should be as illustrated in the image below.

Paste the following code into the `intergrations.yaml` file

name: Continuous Integration and Delivery # workflow name

on:  # this is the entry point to the events, we specificy when the actions should be run
  push:
    branches: [develop]  #specificy  which branch should the workflow be triggered
  pull_request:
    branches: [develop] #specificy  which branch should the workflow be triggered

jobs:
  testing-docker-compose-auto-deploy-digital-ocean: # name of  the job 
    runs-on: ubuntu-latest # specify that the app will run on the latest version of ubuntu
    steps:  # steps that should be followed while  building and deploying the image.
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - uses: actions/checkout@v2

3. Dockerize the application.

Create a new file named `Dockerfile` at the root of the project and paste the following lines of code.


# FROM python:3.9.6-alpine 
FROM python:3.10.1-slim-buster 

# WORKDIR 
ENV APP_HOME=/app
RUN mkdir $APP_HOME
RUN mkdir $APP_HOME/staticfiles
WORKDIR $APP_HOME


LABEL maintainer='manulangat1'


LABEL description="This is an application that shows how to create an automatic ci pipepline that deploys the django app to a digital ocean droplet" 

ENV PYTHONDONTWRITEBYTECODE=1

ENV PYTHONUNBUFFERED=1  

ENV DEBUG=False

ENV ENVIRONMENT=staging

RUN apt-get update \
    && apt-get install -y build-essential \
    && apt-get install -y libpq-dev \
    && apt-get install -y gettext \
    && apt-get -y install netcat gcc postgresql \
    && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
    && rm -rf /var/lib/apt/lists/*

RUN pip3 install --upgrade pip 

COPY requirements.txt $APP_HOME/requirements.txt 

COPY . $APP_HOME/
RUN pip3 install -r $APP_HOME/requirements.txt 



COPY /entrypoint /entrypoint
RUN sed -i 's/\r$//g' /entrypoint
RUN chmod +x /entrypoint





ENTRYPOINT ["/entrypoint"]

Once done, create a new file `docker-compose.yaml` and paste the following lines of code.

version: '3.8'

services:
  web:
    build: 
      context: .
      dockerfile: Dockerfile
    command:  gunicorn do-guide.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - static_volume:/home/app/web/staticfiles

    image: manulangat/auto-deploy-do-github-actions:latest
    ports:
      - "8000:8000"
    networks:
      - bms_staging


volumes:
  static_volume:

networks:
  bms_staging:

With this in place, you can now add the steps to build the docker images in the GitHub actions workflow file

      - uses: actions/checkout@v2
      - name: Build the stack
        run: docker-compose  -f docker-compose.yaml up -d --build

4 Deploy to Dockerhub.

We want the latest image to be deployed to docker hub, Login to your docker hub account and create a new repository.

Grab the image name eg manulangat/auto-deploy-do-github-actions:tagname and head over to the workflow file.

In the workflow file, add a step that logins it to docker hub using your username and password, add these as repository secrets on GitHub

Once done with that, head over to the workflow file and add a step that login into Dockerhub.

- name: Login to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

The workflow file should at this point resemble the one below

name: Continuous Integration and Delivery

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  testing-docker-compose:
    runs-on: ubuntu-latest
    steps:
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2

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

      - uses: actions/checkout@v2
      - name: Build the stack
        run: docker-compose  -f docker-compose.yaml up -d --build

      - name: Get docker logs
        run: docker ps

      - name: Login to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

At this point, the step that pushed the built docker image can now be added after the login to Dockerhub step.

- name: Push to dockerhub
        run: |
          docker-compose push # this pushes the  image to the repo that we have defined in the docker-compose.yaml file.

And a step that stops the containers

- name: stop containers
        run: docker-compose -f docker-compose.yaml down --volumes

5. Auto-deploy to digital ocean droplet.

Head over to the digital ocean page and log in, upon logging in, create a Docker droplet, for this tutorial, a shared CPU of 2Gb and 50Gb SSD disk is sufficient. Give the droplet whatever name you see fit and hit the create droplet button.

To configure firewalls and enable port 8000 and port 7000, which our Django app will be using communicate with the outside world, go to the networking tab, then the firewall option. This will redirect you to the page below, fill name, add a custom TCP inbound rule to port 8000 and 7000, attach it to the droplet created above and hit create firewall button.

With the droplet and firewall now up and running, you can now add a step that automatically deploys the django app to the digital ocean droplets, but before that you need to add a few secrets on github repo.

Ssh into your digital ocean droplet

ssh root@YOUR_DO_IP

Run the following command to generate a new ssh key pair that the pipeline will use to connect to the droplet.

ssh-keygen

To get the value of the generated private key navigate to the ssh folder by

cd ~/.ssh

To get the private key, run the following command

cat id_rsa

make sure that you copy the whole of the contents and add it to your repository secrets as `DO_PRIVATE_KEY`.

With that in place, copy the public key

cat id_rsa.pub

and add it to the authorised_keys file

nano authorised_keys

paste the contents and save and close the file.

Go back to the root of the project and create a new folder `django-ci-example`, create a .env file if need be and a 'docker-compose.yaml' file and paste into it the contents of the file into.

nano docker-compose.yaml

And paste in the following lines of code

version: '3.8'

services:
  web:
    build: 
      context: .
      dockerfile: Dockerfile
    command:  gunicorn do-guide.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - static_volume:/home/app/web/staticfiles

    image: manulangat/auto-deploy-do-github-actions:latest
    ports:
      - "8000:8000"
    networks:
      - django-ci-example


volumes:
  static_volume:

networks:
  django-ci-example:

Head over to your workflow file and add the following step

- name: Executing remote  command and deployment to digital ocean for dev enviroment
        uses: appleboy/ssh-action@master
        with:
          host: "YOUR_DO_IP"
          USERNAME: "root"
          PORT: 22
          KEY: ${{ secrets.DO_PRIVATE_KEY}}
          script: |
            cd django-ci-example/
            docker system prune -af
            docker compose  -f docker-compose.staging.yaml down --volumes
            echo "${{secrets.DOCKER_PASSWORD}}" | docker login -u ${{secrets.DOCKER_USERNAME}} --password-stdin
            docker system prune -af
            docker compose -f docker-compose.staging.yaml pull
            docker compose -f docker-compose.staging.yaml  up --build --remove-orphans -d --force-recreate
            # docker-compose -f docker-compose.staging.yaml up  --build  -d

At this point, your actions file should be similar to the one below.

name: Continuous Integration and Delivery

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  testing-docker-compose:
    runs-on: ubuntu-latest
    steps:
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2

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

      - uses: actions/checkout@v2
      - name: Build the stack
        run: docker-compose  -f docker-compose.yaml up -d --build

      - name: Get docker logs
        run: docker ps

      - name: Login to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Push to dockerhub
        run: |
          docker-compose push

      - name: stop containers
        run: docker-compose -f docker-compose.yaml down --volumes

      - name: Executing remote  command and deployment to digital ocean for dev enviroment
        uses: appleboy/ssh-action@master
        with:
          host: "YOUR_DO_IP"
          USERNAME: "root"
          PORT: 22
          KEY: ${{ secrets.DO_PRIVATE_KEY}}
          script: |
            cd django-ci-example/
            docker system prune -af
            docker compose  -f docker-compose.staging.yaml down --volumes
            echo "${{secrets.DOCKER_PASSWORD}}" | docker login -u ${{secrets.DOCKER_USERNAME}} --password-stdin
            docker system prune -af
            docker compose -f docker-compose.staging.yaml pull
            docker compose -f docker-compose.staging.yaml  up --build --remove-orphans -d --force-recreate
            # docker-compose -f docker-compose.staging.yaml up  --build  -d

To test this out, make commit and push your works and the actions should be triggered. Once the actions is done, navigate to the web page ie `YOUR_DO_IP:7000` and you should be welcomed with a page as shown below.

The code for this project can be found at this GitHub repo .

Kindly follow me to get instant notifications whenever I post articles and guides on Devops and Django.

Happy hacking.