Despliegue de aplicación de Django con Github Actions para un servidor propio
Tags: githubactions, django, deploy, cicd
Published: 2024-06-03 14:42:30


Para proyectos en donde la actualización del código es algo recurrente, es ideal realizar un proceso de automatización del despliegue del proyecto, además de asegurarse que este se subió correctamente. Esto se conoce como CI/CD (Integración continua/Despliegue continuo).

Hay varias formas de realizar este proceso para una aplicación de Django, por ejemplo se pueden utilizar contenedores de Docker, automatización con SSH, usando herramientas como Jenkins, etc. Sin embargo, para esta publicación se utilizará la herramienta de Github Actions para realizar este proceso.

Para desplegar este proyecto se utilizará también: Nginx como servidor web/Proxy, Supervisor que es un controlador de procesos, Gunicorn para servir la app y Postgresql que será el gestor de la base de datos.

Esta publicación es un ejemplo básico para subir una aplicación de Django usando Github Actions junto con un testeo previo, para verificar que el código este correctamente configurado antes de realizar el deploy.

## Paso 1. Creación de un usuario en el servidor

Para utilizar Github Actions en Ubuntu, se debe utilizar un usuario que no sea el usuario root, si ya tienes otro usuario puedes saltar este paso.

Se creará el usuario `ubuntu`, para esto ingresamos al servidor que contratamos y como usuario root ejecutamos el siguiente comando:

```bash
sudo adduser ubuntu
```

### Paso 1.1: Se agrega al usuario dentro del grupo sudo

```bash
sudo usermod -aG sudo ubuntu
```


### Paso 1.2 (opcional): Configurar sudo para que no necesite contraseña

Solo en caso que se requiera que el nuevo usuario no requiera contraseña al ejecutar sudo, se puede editar el archivo de configuración de sudo:

```bash
sudo visudo
```

En el final del archivo se debe agregar la siguiente línea:

```
ubuntu ALL=(ALL) NOPASSWD:ALL
```

### Paso 1.3: Ingresar con el nuevo usuario

Ahora puedes ingresar al servidor mediante SSH con el nuevo usuario creado.

## Paso 2. Hacer las instalaciones necesarias en el servidor

Primero actualizaremos el servidor y todos sus paquetes ejecutando el siguiente comando:

```bash
sudo apt-get update
sudo apt-get upgrade
```

Una vez actualizado el servidor, instalaremos los paquetes necesarios para realizar el despliegue

```bash
sudo apt install python3-pip python3-venv nginx git supervisor nano postgresql postgresql-contrib
```

## Paso 3: Creación de la base de datos

Para manejar la base de datos del proyecto, usaremos PostgreSQL.

### Paso 3.1: Ingresar a la terminal de Postgres

Para ingresar a la terminal de Postgres sin necesidad de cambiar de usuario, se puede ejecutar el siguiente comando:

```bash
sudo -u postgres psql
```

Este comando nos abrirá la terminal de Postgres y veremos algo así:

```postgres
postgres=#
```

### Paso 3.2: Creación de la base de datos y usuario configurado en Django

Una vez dentro de la terminal de Postgres, primero crearemos el usuario, se debe reemplazar "username" y "password" por la configuración que tienes en tu proyecto:

```sql
CREATE USER username WITH PASSWORD 'password';
```

Ahora creamos la base de datos:

```sql
CREATE DATABASE db_name;
```

Para asignarles privilegios un usuario para el uso de la bbdd:

```sql
GRANT ALL PRIVILEGES ON DATABASE db_name TO username;
```

Para establecer un usuario como propietario de la bbdd:

```sql
ALTER DATABASE db_name OWNER TO username;
```

Para salir de la terminal de postgres se ejecuta lo siguiente:

```sql
\q
```

## Paso 4: Realizar la configuración de Github Actions Runner para Self Host

### Paso 4.1: Crear Runner en Github:

Dentro de la sección `"Settings"` del repositorio de nuestro proyecto


![Seccion settings github](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vbtbpfu8lmsl0m5rrzfd.png)

se debe ir a Actions y luego a Runners.


![Actions runners](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmktqm6d9yvt2mjqh2xt.png)

Una vez en Runners, se debe hacer clic en el botón `New self-hosted runner`. Se abrirá una nueva página con las instrucciones necesarias para configurar el servidor linux.


![Eleccion de servidor linux](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhfwearo3qmjxqu0sotu.png)

### Paso 4.2: Realizar la configuración en el servidor

Copiamos los comandos de la sección `Download` uno por uno y lo vamos ejecutando en el servidor siguiendo las instrucciones que aparece en pantalla. Si se quiere manejar los nombres por default, se debe apretar enter cuando al ejecutar un comando se pida algún texto.

Copiamos el primer comando de la sección `Configure` para crear el Runner. Si todo sale bien hasta este punto, puedes ejecutar los siguientes comandos para indicarle al runner que mantenga corriendo el servicio:

```bash
sudo ./svc.sh install
sudo ./svc.sh start
sudo ./svc.sh status
```

Finalmente para verificar que todo esta correcto se puede ejecutar:

```bash
./run.sh
```

Se debiese imprimir en la consola que la conexión con Gihtub es un éxito, para salir del run se puede apretar `ctrl + c`.

## Paso 5: Configuración de Supervisor

Primero crearemos el archivo de configuración de nuestro servicio gunicorn, para esto nos movemos al directorio conf.d dentro de la carpeta de supervisor:

```bash
cd /etc/supervisor/conf.d
```

Una vez dentro, creamos el archivo gunicorn.conf que contendrá las instrucciones que debe servir supervisor:

```bash
sudo nano gunicorn.conf
```

A continuación mostraré un ejemplo de lo que debe estar escrito dentro del archivo. Lo importante es verificar que las rutas estén escritas correctamente, para esto debes cambiar `{name_repository}`por el nombre de tu repositorio y `myproject` por el nombre de tu proyecto django:

```
[program:gunicorn]
directory=/home/ubuntu/actions-runner/_work/name_repository/name_repository
command=/home/ubuntu/actions-runner/_work/name_repository/name_repository/venv/bin/gunicorn --workers 3 --bind unix:/home/ubuntu/app.sock myproject.wsgi:application  
autostart=true
autorestart=true
stderr_logfile=/var/log/gunicorn/gunicorn.err.log
stdout_logfile=/var/log/gunicorn/gunicorn.out.log

[group:gunicorn]
programs:gunicorn
```

Para guardar los cambios apretamos `ctrl + o`, y para volver a la terminal `ctrl + x`.

Ahora creamos el directorio donde se escribirán los logs de gunicorn:

```bash
sudo mkdir /var/log/gunicorn
```

Para que supervisor lea el archivo de configuración recién creado, se lo indicamos con el siguiente comando:

```bash
sudo supervisorctl reread
```

Nos debería aparecer la siguiente respuesta

```bash
gunicorn: available
```

Por último, para que supervisor ejecute los cambios recién leidos, ejecutamos:

```bash
sudo supervisorctl update
```

Al verificar si el servicio gunicorn esta corriendo correctamente al ejecutar el siguiente comando:

```bash
sudo supervisorctl status
```

Debiese aparecer un error `FATAL` con la descripción: `can't find command '/home/ubuntu/actions-runner/_work/name_repository/name_repository/venv/bin/gunicorn'`, ya que aun no hemos subido el código a nuestro servidor, eso lo haremos a continuación.

## Paso 6: Configuración del workflow de Github Actions

Para este caso se mostrará un ejemplo de como realizar el workflow separandolo en 2 pasos:
1. Realizar tests para verificar que el código se suba correctamente.
2. Si los tests pasaron correctamente, se subirá el código en el servidor.

### Paso 6.1: Creación del archivo YAML de Github Actions

En la raíz del repositorio de github, crearemos la carpeta `.github` y dentro de esta la carpeta `workflows`. Dentro de esta última carpeta crearemos un archivo con la extensión `.yml`, por ejemplo `ci-cd.yml`. Por lo que, el repositorio debiese tener una estructura básica como se muestra a continuación:

```
.
├── .github
│   └── workflows
│       └── ci-cd.yml
├── myproject
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── requirements.txt
│
...
```

Dentro del archivo `ci-cd.yml` escribimos las instrucciones que necesita Github Actions para automatizar las tareas:

```yml
name: CI CD Django

on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest  # Puedes utilizar un runner predeterminado de GitHub Actions para los tests

    services:
      postgres:
        image: postgres:13
        env:
          POSTGRES_DB: db_name
          POSTGRES_USER: username
          POSTGRES_PASSWORD: password
        ports:
          - 5432:5432

    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.10.12]

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: |
          python -m venv venv
          source venv/bin/activate
          pip install -r requirements.txt

      - name: Run migrations
        run: |
          source venv/bin/activate
          python manage.py migrate

      - name: Run tests
        run: |
          source venv/bin/activate
          python manage.py test

  deploy:
    needs: test  # Este trabajo solo se ejecuta si el trabajo 'test' pasa
    runs-on: self-hosted

    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.10.12]

    steps:
      - name: Checkout main branch
        uses: actions/checkout@v3
        with:
          clean: false

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies # Se instala gunicorn pero debiese estar dentro de requiriments.txt
        run: |
          python -m venv venv
          source venv/bin/activate
          pip install -r requirements.txt
          pip install gunicorn

      - name: Run migrations
        run: |
          source venv/bin/activate
          python manage.py migrate

      - name: Reset supervisor
        run: sudo service supervisor restart
```

### Paso 6.2: Subir el archivo YML a Github para iniciar Github Actions

Hacemos un commit y un push de los cambios para subir nuestra configuración.

Al realizar un push, Github inmediatamente reconocerá el workflow y comenzará a realizar las tareas que se indicaron. Esto lo puedes visualizar dentro de la sección `Actions` en tu repositorio de Github.


![Seccion actions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vhh24khyjhqs9kne1kr.png)

En caso que la operación sea un éxito, se mostrará un check verde al lado del nombre de la tarea, en caso contrario, aparecerá una x con color rojo.

### Paso 6.3: Verificamos que el proyecto se subió al servidor

En caso que la operación haya resultado exitosa, podemos ver que dentro de la carpeta `actions-runner` se creo una carpeta llamada `_work`, esta carpeta contiene nuestro repositorio, para este caso `name_repository`, por lo tanto el directorio de la aplicación de Django debiese estar en la siguiente altura:

```
cd /home/ubuntu/actions-runner/_work/name_repository/name_repository
```

Dentro de esta carpeta podemos encontrar la carpeta `venv` que contendrá el entorno virtual del proyecto, por lo que podemos activarla y generar comandos con `managa.py` como por ejemplo, crear un superusario.

Ahora podemos verificar si Supervisor reconoció el proyecto y se encuentra corriedo, para esto ejecutamos:

```bash
sudo supervisorctl status
```

Si aparece que el servicio esta corriendo `RUNNING`, es porque el archivo y el código se encuentran configuradas correctamente. En caso de error, verifica las rutas dentro del archivo gunicorn.conf

## Paso 7: Configuración de Nginx

### Paso 7.1: Editar nginx.conf

Para configurar Nginx, primero debemos modificar el usuario que tiene configurado por defecto, para esto nos movemos dentro de la carpeta de nginx:

```bash
cd /etc/nginx
```

Una vez ahí, editamos el archivo nginx.conf de la siguiente forma:

```bash
sudo nano nginx.conf
```

Al principio del archivo, aparecerá la siguiente instrucción:

```
user www-data;
# resto de la configuracion
```

Borramos la parte que dice www-data y la cambiamos por root:

```
user root;
# resto de la configuracion
```

Guardamos los cambios con `ctrl + o` seguido de `ctrl + x`.

### Paso 7.2: Configurar Nginx para subir nuestro proyecto

Para configurar Nginx e indicarle que tiene que desplegar nuestro proyecto, nos vamos a la siguiente ruta:

```bash
cd /etc/nginx/sites-available
```

Una vez aquí, creamos un arhivo de configuración, que contendra todas las instrucciones necesarias para que nginx sirva nuestra aplicación, el nombre del archivo puede ser cualquiera pero con extensión `.conf`. Ejemplo:

```bash
sudo nano django.conf
```

A continuación, mostraré un ejemplo básico de como configurar el archivo, pero pueden haber muchas más configuraciones para personalizar el funcionamiento de Nginx. Ejemplo:

```nginx
server{

 listen 80;
 server_name 198.23.227.182; # Aqui va el dominio. Ejemplo: midominio.com o una dirección IP (no recomendado)

 
 location / {
  include proxy_params;
  proxy_pass http://unix:/home/ubuntu/app.sock;
 }

  location /static {
      alias /home/ubuntu/actions-runner/_work/name_repository/name_repository/static;
  }

  location /media {
      alias /home/ubuntu/actions-runner/_work/name_repository/name_repository/media;
  }

}
```

Deberías verificar que las rutas a los archivos estáticos y de media sea correcta cambiando `name_repository`, por el nombre de tu repositorio.

El dominio indicado, debe estar configurado dentro del archivo settings.py de Django en ALLOWED_HOSTS. Ejemplo:

```python
ALLOWED_HOSTS = ['*']  # El * permite cualquier HOST (no recomendado). Aquí debiese ir tu dominio
```

Para verificar que la sintaxis del archivo este escrita correctamente, ejecutamos:

```bash
sudo nginx -t
```

Debiese aparecer un mensaje como el siguiente:

```bash
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```

Ahora ejecutamos el siguiente comando para crear un enlace simbólico que referenciara al archivo de configuración recién creado:

```bash
sudo ln /etc/nginx/sites-available/django.conf /etc/nginx/sites-enabled
```

Reiniciamos el servicio de nginx:

```bash
sudo service nginx restart
```

Y si ahora buscamos el dominio ingresado dentro de nuestro navegador, debiésemos ver nuestro proyecto desplegado.

## Conclusiones

Siguiendo estos pasos, podrás tener tu proyecto de Django desplegado usando las herramientas mencionadas en este artículo.

Hay varias formas de realizar un despliegue usando Github Actions y Django, lo importante es realizar testeos previos a realizar el deploy de tu proyecto, para que no existan errores en producción.

Espero que te haya sido de utilidad :) Cualquier duda, consulta o sugerencia, házmela llegar a través de los comentarios.