logo cosasdedevs
Deploy de un proyecto hecho en Django en un VPS con Ubuntu 20.04, Nginx, Gunicorn y PostgreSQL

Deploy de un proyecto hecho en Django en un VPS con Ubuntu 20.04, Nginx, Gunicorn y PostgreSQL



My Profile
Nov 03, 2021

Mucha gente lo ha pedido y por fin está aquí 🎉. Después de terminar los tutoriales que quería hacer sobre Python (por el momento), es hora de retomar otras cositas que también pueden ser interesantes para vosotr@s. En el tutorial de esta semana vamos a ver paso a paso como realizar el deploy de un proyecto hecho en Django en un VPS con Ubuntu 20.04, Nginx, Gunicorn y PostgreSQL.

Antes de empezar...

Para realizar este tutorial necesitaremos dos cosas. Lo primero será un vps o un server del que tengamos un usuario con permisos de administración y el sistema operativo Ubuntu. Hay varios proveedores en los que podéis contratar un vps barato. Para este tutorial yo he contratado el vps más barato que tenía OVH que son unos 3.5 €, pero vamos, podéis contratarlo donde queráis. Lo importante es tener un usuario con permisos de administración y Ubuntu.

Lo segundo será el proyecto en un repositorio de git, ya sea GitHub, GitLab o Bitbucket. En mi caso, vamos a realizar el deploy de este proyecto que creé en un tutorial pasado en el que creamos un blog con Django desde 0.

Opcionalmente un dominio para que apunte a nuestro servidor y podamos activar https.

Paso 1: Comprobar la configuración del proyecto

En el ámbito del proyecto necesitaremos algunas cositas. Lo primero es comprobar que Django está configurado para utilizar PostgreSQL. Para ello debemos ir al archivo settings.py que está dentro de la carpeta con el nombre de nuestro proyecto. Ahí vamos a la constante DATABASES y la modificamos para indicarle a Django que base de datos vamos a utilizar:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': env('POSTGRESQL_NAME'),
        'USER': env('POSTGRESQL_USER'),
        'PASSWORD': env('POSTGRESQL_PASS'),
        'HOST': env('POSTGRESQL_HOST'),
        'PORT': env('POSTGRESQL_PORT'),
    }
}

Además del ENGINE (que es el que indica que tipo de base de datos estamos utilizando), debemos añadir, el host, nombre de la base de datos, user, password y el puerto.

Por seguridad yo guardo esa información en un archivo .env que nunca se sube al repositorio. Por eso no aparecen esos datos en crudo en el bloque de código que tenéis más arriba.

Si queréis más información acerca de las variables de entorno y como implementarlas en un proyecto con Django, pinchad en este tutorial en el que explico como conectarse a una bbdd de PostgreSQL con Django y como utilizar las variables de entorno. También os dejo este post en Stack Overflow donde explican que son y como funcionan.

También debeis comprobad si en el archivo settings.py tenéis configuradas las constantes para los archivos estáticos y la constante ALLOWED_HOSTS. Me explico:

Para que funcione el proyecto en producción, debemos tener seteado dentro de ALLOWED_HOSTS la ip de nuestro server y/o el dominio. También valdría asterísco que significaría que permite cualquier dominio o ip:

ALLOWED_HOSTS = ['*']
# o así también valdría
ALLOWED_HOSTS = ['51.68.46.172', 'albertorcaballero.com']

Si no es así, cuando intentemos acceder desde el navegador no nos dejará.

Para poder acceder a los archivos estáticos deberemos tener configuradas las constantes donde se indican las rutas donde se almacenan estos archivos. Para verficiarlo, deberíais tener algo así en vuestro archivo settings.py. Si no es así, añadidlo:

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/admin')
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)

Recordad haced estos pasos porque más adelante cuando clonemos el proyecto en el server, no vamos a poder hacer un git pull y nos tocará borrar y clonar de nuevo el proyecto.

Paso 2: Preparar nuestro servidor

Lo primero que vamos a hacer es facilitar el proceso para entrar en nuestro server. Lo normal es usar el siguiente comando y luego tendremos que añadir la contraseña:

ssh {mi-usuario}@{ip-servidor}

El proceso que vamos a realizar a continuación es totalmente opcional así que podeís pinchar aquí para saltaros esta parte.

Bien, seguís por aquí jaja, lo primero que vamos a hacer es crear una llave ssh. Para ello vamos a la terminal y lanzamos el siguiente comando:

ssh-keygen

Nos preguntará donde queremos guardarla. Lo mejor es guardarla dentro de la carpeta ~/.ssh y la podéis llamar como queráis. Yo la he llamado vps_tutorial.

El siguiente paso es añadir un passphrase que ahí ya os lo dejo a vuestra elección si queréis añadirlo. Listo, ya tenemos una llave para nuestro vps.

Generating public/private rsa key pair.
Enter file in which to save the key (/home/alber/.ssh/id_rsa): /home/alber/.ssh/vps_tutorial
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/alber/.ssh/vps_tutorial.
Your public key has been saved in /home/alber/.ssh/vps_tutorial.pub.

El siguiente paso es enviar la clave pública que acabamos de crear a nuestro server. Esto lo haremos con el siguiente comando:

ssh-copy-id -i /home/alber/.ssh/vps_tutorial.pub {mi-usuario}@{ip-servidor}

Una vez enviada la clave, debemos dirigirnos a la carpeta .ssh (en mi caso es /home/alber/.ssh) y crear un archivo al que llamaremos config (sin ninguna extensión). Dentro de él añadiremos el siguiente contenido:

ServerAliveInterval 120
ServerAliveCountMax 3

Host vps_tutorial
        HostName ip-servidor
        User mi-usuario
        IdentityFile /home/alber/.ssh/vps_tutorial

ServerAliveInterval y ServerAliveCountMax lo utilizaremos para mantener activa la conexión. Después escribimos Host y el nombre que le queramos dar a la configuración, en mi caso lo he llamado vps_tutorial al igual que la clave ssh. En HostName indicamos la ip del servidor al que nos queremos conectar. En User el nombre del usuario con el que nos conectaremos y en IdentityFile la ruta de nuestra clave privada.

Guardamos el archivo y a partir de ahora podremos conectarnos a nuestro server simplemente lanzando el siguiente comando:

ssh {nombre_host}
# En mi caso sería así:
ssh vps_tutorial

Puede ser que la primera vez que lo utilicéis os dé problemas. Para solucionarlo, os conectáis de la forma normal (ssh {tu_usuario}@{ip_del_servidor}), salís y lo volvéis a intentar con el nuevo comando y ya deberíais entrar sin problema.

Una vez dentro del servidor, lo primero que haremos será actualizar a sus nuevas versiones los paquetes que vienen instalados por defecto en nuestro servidor:

sudo apt update && sudo apt upgrade -y

Esto es opcional, pero si queréis cambiar la hora del servidor lo podréis hacer con el siguiente comando:

sudo timedatectl set-timezone Europe/Madrid

Solo debéis sustituir Europe/Madrid por vuestra zona horaria. Os dejo la lista pinchando aquí.

Ahora vamos a configurar el Firewall que viene por defecto en Ubuntu. Para ello primero vamos a habilitar los permisos para OpenSSH que es el paquete que utilizamos para realizar el login, esto es necesario, ya que cuando habilitemos el Firewall solo podremos acceder a nuestro servidor si le hemos dado los permisos:

sudo ufw allow "OpenSSH"

Una vez hecho esto, habilitamos el Firewall:

sudo ufw enable

El siguiente paso que vamos a realizar es instalar los paquetes que necesitaremos como Python 3, Nginx y PostgreSQL:

sudo apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx curl

Paso 3: Configurar PostgreSQL

Ahora que ya tenemos instalado PostgreSQL, es hora de crear nuestra base de datos y el usuario que usaremos para poder trabajar con ella. Para ello, lo primero que haremos será acceder a la bbdd con el siguiente comando:

sudo -u postgres psql

Después creamos el usuario que utilizará nuestro proyecto para acceder a la bbdd:

CREATE USER {nombre-usuario} WITH PASSWORD '{contraseña}';
# Ejemplo
CREATE USER simpleblog WITH PASSWORD 'rJgtSf5fCXmdB4wH';

Os dejo esta web para que generéis contraseñas difíciles https://passwordsgenerator.net/

El siguiente paso es crear la bbdd. Yo la he llamado simpleblog y su propietario será el usuario que acabamos que crear también llamado simpleblog:

CREATE DATABASE simpleblog
    WITH 
    OWNER = simpleblog
    ENCODING = 'UTF8'
    TABLESPACE = pg_default
    CONNECTION LIMIT = -1;

Por último le damos a nuestro usuario todos los privilegios a la hora de trabajar con la bbdd para evitar problemas:

GRANT ALL PRIVILEGES ON DATABASE simpleblog TO simpleblog;

Una vez hecho esto, escribimos exit y pulsamos enter para salir de PostgreSQL.

Paso 4: Descargar nuestro proyecto con Git

Al instalar Nginx, este nos crea un usuario llamado www-data. Este usuario será el que se utilizará cuando accedamos a nuestra web desde el navegador, por lo tanto será el usuario que utilizaremos para clonar el proyecto desde GitHub (o desde donde tengáis el repositorio).

En mi caso, al ser un repositorio público, no haría falta tener una clave ssh, pero como vamos a seguir los pasos como si fuera un repositorio privado, vamos a crearla.

Para ello, primero creamos la carpeta .ssh dentro de la carpeta principal del usuario www-data. Esta se encuentra en la ruta /var/www. Para crearla lanzamos el siguiente comando:

sudo mkdir /var/www/.ssh

El siguiente paso es cambiar el propietario de la carpeta, ya que la hemos creado con root. Para ello lanzamos el siguiente comando:

sudo chown -R www-data /var/www/.ssh

Una vez hecho esto, vamos a crear nuestra clave pública y privada. Para ello lanzamos el siguiente comando sustituyendo email@email.com por el email con el que estéis registrado en la plataforma donde tengáis vuestro repositorio (GitHub, GitLab, etc.).

sudo -u www-data ssh-keygen -t rsa -b 4096 -C "email@email.com"

 Al igual que cuando creamos la clave en nuestro PC, os debería salir algo como esto. Fijaos en que en este caso no vamos a renombrar nuestra clave, sino que mantendremos la ruta y el nombre por defecto (/var/www/.ssh/id_rsa):

Generating public/private rsa key pair.
Enter file in which to save the key (/var/www/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /var/www/.ssh/id_rsa
Your public key has been saved in /var/www/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:xxxxx email@email.com

Ahora copiamos el contenido de nuestra clave pública y la guardamos en la plataforma donde tenemos nuestro repositorio. Para ver la clave pública debemos lanzar el siguiente comando:

cat /var/www/.ssh/id_rsa.pub

Una vez hecho esto, podemos comprobar que todo ha ido bien con el siguiente comando si nuestro repositorio está en GitHub. Para otras plataformas deberéis sustituir "git@github.com" por el correspondiente a esa plataforma:

sudo -u www-data ssh -T git@github.com

Si todo ha ido bien debería aparecer el siguiente mensaje:

Hi albertorc87! You've successfully authenticated, but GitHub does not provide shell access.

Antes de descargar el proyecto debemos asegurarnos de que el usuario www-data es el propietario de la carpeta /var/www, para ello, al igual que lo hicimos con la carpeta .ssh, lanzamos el siguiente comando:

sudo chown -R www-data /var/www

Una vez hecho esto, clonamos el proyecto dentro de la carpeta /var/www. Para ello nos dirigimos a esa ruta y lanzamos el siguiente comando, en el que deberéis sustituir {repositorio} por la url de vuestro repositorio. En {nombre-carpeta} yo le voy a dar el nombre del dominio que voy a utilizar, en este caso el dominio albertorcaballero.com, que actualmente lo tengo en desuso:

sudo -u www-data git clone {repositorio} {nombre-carpeta}
# Mi caso:
sudo -u www-data git clone git@github.com:albertorc87/simple_blog.git albertorcaballero.com

Paso 5: Configuración del proyecto

Para configurar nuestro proyecto lo primero que haremos será instalar el paquete de Python para crear el entorno virtual:

sudo apt install python3-venv

Después vamos al directorio de nuestro proyecto (en mi caso /var/www/albertorcaballero.com) y lanzamos el siguiente comando para crear el entorno virtual:

sudo -u www-data python3 -m venv env

Ahora tenemos que hacer un cambio momentáneo. Para poder instalar las dependencias con el entorno virtual tenemos que estar logeados con el usuario www-data, al no poder hacer eso, vamos a cambiar por un momento el propietario de la carpeta en la que está nuestro proyecto por el usuario logueado:

sudo chown -R mi-usuario /var/www/albertorcaballero.com

Ahora activamos el entorno virtual:

source env/bin/activate

Después solo debemos instalar las dependencias de nuestro proyecto. Para ello tengo un archivo llamado requirements.txt. Si no lo tenéis y queréis crearlo para guardar vuestras dependencias, lo que debéis hacer es en el PC donde tengáis funcionado el proyecto, lanzáis el comando "pip freeze" y copiais el listado de dependencias en el archivo requirements.txt. Después solo debéis subirlo al repositorio, haced un git pull en el server y ya podréis lanzar el siguiente comando:

pip install -r requirements.txt

Aparte debemos instalar la librería de Gunicorn para Python. Esto lo haremos con el siguiente comando:

pip install gunicorn

Devolvemos la propiedad de la carpeta al usuario www-data y listo:

sudo chown -R www-data /var/www/albertorcaballero.com

Si tenéis variables de entorno como es en mi caso, debemos crear el archivo .env y rellenarlas. Para ello vamos nos dirigimos a la carpeta que se llama igual a nuestro proyecto (en mi caso /var/www/albertorcaballero.com/simple_blog) y lanzamos el siguiente comando:

sudo -u www-data nano .env

Dentro añadís vuestras variables de entorno. Yo por ejemplo tendría que añadir la autenticación a PostgreSQL y la SECRECT_KEY y listo:

# Ejemplo de mi archivo .env
SECRET_KEY=my_key

POSTGRESQL_NAME=simpleblog
POSTGRESQL_USER=simpleblog
POSTGRESQL_PASS=password
POSTGRESQL_HOST=localhost
POSTGRESQL_PORT=8754

Ahora que ya tenemos la configuración de la bbdd, es hora de crear las tablas. Para ello volvemos a la raíz del proyecto y lanzamos el siguiente comando:

python manage.py migrate

Una vez hecho esto, creamos un superusuario para poder acceder al panel de administración de Django. Esto lo haremos con el siguiente comando:

python manage.py createsuperuser

Ahora vamos a comprobar que funciona nuestro proyecto. Primero habilitamos el puerto en el firewall para poder acceder a el:

sudo ufw allow 8000

 El siguiente paso es lanzar el siguiente comando en la ráiz del proyecto:

python manage.py runserver 0.0.0.0:8000

Ahora en nuestro navegador solo debemos escribir la ip del servidor seguido de dos puntos y el puerto ya deberíamos poder ver nuestro proyecto funcionando:

# En mi caso sería esta ip:
http://51.68.46.172:8000/

Paso 6: Configuración de Gunicorn

Con el proyecto ya configurado, podemos desactivar el entorno virtual con el comando deactivate:

deactivate

También podemos eliminar el permiso que creamos para el puerto 8000:

sudo ufw delete allow 8000

Ahora vamos a configurar Gunicorn. El primer paso es crear el archivo gunicorn.socket:

sudo nano /etc/systemd/system/gunicorn.socket

Añadimos la siguiente información, guardamos y cerramos:

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Ahora creamos el archivo gunicorn.service:

sudo nano /etc/systemd/system/gunicorn.service

Y añadimos el siguiente código:

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target


[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/{mi-proyecto}
ExecStart=/var/www/{mi-proyecto}/env/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
          {nombre-proyecto}.wsgi:application



[Install]
WantedBy=multi-user.target

El que debemos reemplazar {mi-proyecto} por el nombre de la carpeta donde está vuestro proyecto y {nombre-proyecto} por el nombre de vuestro proyecto. Os dejo el ejemplo de como queda el mio:

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target


[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/albertorcaballero.com
ExecStart=/var/www/albertorcaballero.com/env/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
          simple_blog.wsgi:application



[Install]
WantedBy=multi-user.target

El siguiente paso es activar Gunicorn:

sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket

Para comprobar que todo ha ido bien, lanzamos el siguiente comando:

sudo systemctl status gunicorn.socket

La respuesta debería ser similar a esta:

● gunicorn.socket - gunicorn socket
     Loaded: loaded (/etc/systemd/system/gunicorn.socket; enabled; vendor preset: enabled)
     Active: active (listening) since Sun 2021-10-31 12:22:37 CET; 56s ago
   Triggers: ● gunicorn.service
     Listen: /run/gunicorn.sock (Stream)
      Tasks: 0 (limit: 2286)
     Memory: 0B
     CGroup: /system.slice/gunicorn.socket

Oct 31 12:22:37 vps-685b8378 systemd[1]: Listening on gunicorn socket.

Ahora debemos confirmar que existe el archivo /run/gunicorn.sock con el siguiente comando:

file /run/gunicorn.sock

Si todo ha ido bien debería devolver esto:

/run/gunicorn.sock: socket

Si no es así o hay algún fallo, revisad los pasos anteriores para comprobar que está todo ok.

El siguiente paso es comprobar que el socket puede recibir peticiones, para ello lanzamos el siguiente comando:

curl --unix-socket /run/gunicorn.sock localhost

Esto nos devolverá un html con el index de nuestra web.

Una vez hecho esto, podemos comprobar que el servicio de Gunicorn está listo con el siguiente comando:

sudo systemctl status gunicorn

Paso 7: Configuración de Nginx

Ánimo, ya queda poco para terminar el tutorial. Ahora vamos a configurar Nginx. El primer paso será crear un archivo con la configuración de Nginx para nuestro proyecto. Para ello lanzamos el siguiente comando al que yo lo he llamado como el nombre de mi dominio. Os aconsejo que lo nombréis con el nombre de vuestro dominio porque lo necesitaremos más adelante en los certificados:

sudo nano /etc/nginx/sites-available/albertorcaballero.com

Dentro de el, debeís añadir la siguiente configuración que os explicaré a continuación:

server {
    listen 80;
    server_name albertorcaballero.com;

    location ~ /\.ht {
            deny all;
    }
    location ~ /\.git {
            deny all;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    # Django media
    location /media  {
            alias /var/www/albertorcaballero.com/media/;  # your Django project's media files - amend as required
    }

    location /static {
            alias /var/www/albertorcaballero.com/static; # your Django project's static files - amend as required
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }

}

server {
    server_name www.albertorcaballero.com;
    return 301 https://albertorcaballero.com$request_uri;
}

En server_name añado mi dominio, si aun no disponéis de uno, podéis poner la ip.

Después controlamos los accesos con location y la ruta.

En los dos primeros casos, añado un control para que no se pueda acceder al directorio .git y al .htaccess.

También hay un control para evitar los clásicos avisos de que falta el archivo /favicon.ico.

También añadimos las direcciones donde se encuentran nuestros archivos estáticos y multimedia.

Por último, apuntamos que todo el tráfico vaya a Gunicorn.

En el segundo bloque server indicamos que si las peticiones llegan desde www.albertorcaballero.com, sean redirigidas al dominio sin el www. Por el momento no tenemos https pero en la redirección ya lo he dejado preparado así porque los vamos a instalar en cuanto terminemos de configurar Nginx.

Guardamos y habilitamos el site con el siguiente comando en el que añadimos el archivo que acabamos de editar y creamos un enlace simbólico a la carpeta sites-enabled:

sudo ln -s /etc/nginx/sites-available/albertorcaballero.com /etc/nginx/sites-enabled

Para comprobar que todo ha ido bien lanzamos el siguiente comando:

sudo nginx -t

Si todo ha ido bien reiniciamos Nginx:

sudo systemctl restart nginx

Después damos permisos a Nginx para el Firewall

sudo ufw allow 'Nginx Full'

Ahora debéis ir a la web donde comprásteis el dominio y realizar los cambios correspondientes para que el dominio apunte a la ip del servidor. Si no tenéis muy claro como hacer el proceso seguro que vuestro proveedor tiene tutoriales o documentación para hacerlo.

Yo os dejo captura de como lo he hecho con namecheap que es donde tengo el dominio:

La parte que véis pixelada sería la ip de mi servidor.

Si todo ha ido bien, ya podéis escribir el dominio en vuestro navegador y os debería aparecer vuestro proyecto funcionando al 100%

Podríamos decir que ya está terminado pero no. Nos falta algo muy importante que es poner los certificados para poder usar https. Por suerte hoy en día se pueden conseguir de forma gratuita

Para ello en nuestro server lanzamos el siguiente comando para instalar Certbot que podemos utilizar para instalar certificados gratuitos y que estos se renueven de forma automática:

sudo snap install --classic certbot

Después vamos a generar los certificados con el siguiente comando. Recordad sustituir mi dominio por el vuestro:

sudo certbot --nginx --hsts --staple-ocsp --must-staple -d albertorcaballero.com -d www.albertorcaballero.com

Seguimos los pasos y listo, este comando editará la configuración de Nginx para que siempre redirija a https, así que ahora solo tenéis que recargar la página y ya se mostrará vuestro proyecto como una web segura 💪.

Listo, ya tenemos nuestro server configurado al 100%. El siguiente paso sería automatizar el deploy cada vez que subamos un cambio al repositorio. Si usais GitHub, os dejo este tutorial donde explico como hacerlo.

Y eso es todo por este tutorial. Espero que os haya gustado y como siempre, os recomiendo seguirme en Twitter para estar al tanto de los nuevos tutoriales y ahora también podéis seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.

Nos leemos 👋.

225 vistas

🐍 Sígueme en Twitter

Si te gusta el contenido que subo y no quieres perderte nada, sígueme en Twitter y te avisaré cada vez que cree contenido nuevo 💪
Luego ¡Te sigo!

Nos tomamos en serio tu privacidad

Utilizamos cookies propias y de terceros para mejorar la experiencia del usuario a través de su navegación. Si pulsas entendido aceptas su uso. Ver política de cookies.