Deploy for kids – Guide for deploying Django Python 3

crianças fazendo deploy

There is a lot of tutorials out there, especially in English. Here it goes another one. I wrote it originally in Portuguese.

The reason many people has problems deploying is that they don’t pay enough attention to details. Deploying is easy when you are familiarized with all parts involved. You must know how to authenticate through ssh, be used to command line and Linux, understand how to configure and set up your project, have an idea of what serving static files is, what is Gunicorn… Ok, it’s not that simple. That’s why there is a lot of deploy tools, kits, and tutorials. Currently, with Ansible, Docker and whatever kids are using these days it’s easier to deploy, but what happens under the hood gets more abstract.

Maybe in a couple of years, this post is going to be obsolete if it’s not already with serverless and everything else. Anyway, just a few people want to learn how to deploy Django as I’ll show here, but if it helps at least one person, I’ll be satisfied.

Enjoy this Old-Style guide!

The Server

I presume you don’t have a server or AWS account, DigitalOcean, Linode… Nothing! You have to create an account in one of them and launch a server with the distro you want. If it’s your first time, don’t go with AWS because it’s way more complicated than the others.

In this tutorial, I’m using an Ubuntu 16.04, the most common distro you’ll see around. You can also pick a Debian if you like.

Initial Set Up

Configure server timezone

sudo locale-gen --no-purge --lang pt_BR  # I'm using pt_BR, because HUE HUE BR BR
sudo dpkg-reconfigure tzdata

Update and upgrade OS Packages:

sudo apt-get update 
sudo apt-get -y upgrade

Installing Python 3.6 over Python 3.5

Replace Python 3.5 which is default on our distro with Python 3.6.

sudo apt-get update
sudo add-apt-repository ppa:jonathonf/python-3.6
sudo apt-get install python3.6
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.5 1
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 2

You can choose which Python version the OS will call when you type python3.

sudo update-alternatives --config python3

Having trouble, take a look here:

How to Install Python 3.6.1 in Ubuntu 16.04 LTS

Install OS requirements

sudo apt-get install python3-pip nginx supervisor git git-core libpq-dev python-dev 
python-virtualenv

If your project has more OS requirements, install them as well.

VirtualEnvWrapper for Python3

I’m a fan of VirtualEnvWrapper. It’s super easy and creates all my virtual environments in the same place. That’s a personal choice, if you don’t like it, use what you know how to use.

First, you install virtualenvwrapper, and then define where to put your virtualenvs. (WORKON_HOME).

If you need to use it with multiple Python versions, you must define VIRTUALENVWRAPPER_PYTHON. Here I’m using always with python3. It’s not a problem since you can create a virtualenv pointing which Python that env will use.

sudo pip3 install virtualenvwrapper
echo 'export WORKON_HOME=~/Envs' >> ~/.bashrc
echo ‘export VIRTUALENVWRAPPER_PYTHON=`which python3`’ >> ~/.bashrc
echo 'source /usr/local/bin/virtualenvwrapper.sh' >> ~/.bashrc
source ~/.bashrc

Now, create your virtualenv and define what Python is going to use.

mkvirtualenv name_venv --python=python3

VirtualEnvWrapper is really easy to use. If you want to activate a virtual env, you can use workon.

workon name_venv

To deactivate this virtualenv:

deactivate

To remove a virtualenv:

rmvirtualenv name_venv

Generate SSH for GitHub Authentication

You don’t want (neither should) write your password to git pull your project on the server.

Generating SSH Keys:

cd ~/.ssh
ssh-keygen -t rsa -b 4096 -C "[email protected]"
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa

See and copy the content of your public key (id_rsa.pub)

cat ~/.ssh/id_rsa.pub

Then sign in your GitHub account and go to Settings > SSH and GPG Keys. Click on New SSH Key, give it a name, like (“test server keys”) and in Key paste the content of your  id_rsa.pub

Clone your Django Project

Copy the SSH link from GitHub to clone your project. In this case, I’m using a project that I just have found as an example.

git clone [email protected]:kirpit/django-sample-app.git

In the project folder, install the project requirements.

Remember that you have to be in your virtual environment

cd django-sample-app/
pip install -r requirements.txt

Now, make the necessary alterations for your deploy, such as create a settings_local.py file, change database settings or anything specific to your project.

After you’re done, run your migrations and collect your static files (if you’re using it).

python manage.py migrate
python manage.py collectstatic

Configuring NGINX

Nginx, like Apache, is an entirely separate world. Right now, you just need the basics.

/etc/nginx/sites-available/ is a directory where you have to put the config files of available sites. There is another directory, /etc/nginx/sites-enabled/ that shows which sites are enabled. They are the same thing, but what is put on enabled will be served by Nginx.

It’s usual to create your config file on sites-available and create just a symlink to sites-enabled.

First of all, I’ll remove the default site from Nginx.

sudo rm /etc/nginx/sites-enabled/default

Now, create the config file for your site. (If you don’t know how to use VIM, use nano instead of vi)

sudo vi /etc/nginx/sites-available/mysite

Past this on your file, changing the necessary paths:

server {
 listen 80;
 access_log /home/username/logs/access.log;
 error_log /home/username/logs/error.log;

 server_name nome-site.com.br;

 location / {
 proxy_pass http://127.0.0.1:8000; 

 proxy_pass_header Server;
 proxy_set_header X-Forwarded-Host $server_name;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header Host $http_host;


 }

 location /static {

   alias /home/username/project_path/static/;

 }

And create a symlink to sites-enabled:

sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/mysite

Restart Nginx:

sudo service nginx restart

Ok, if you made it till here, if you access your website you will see a 502 Bad Gateway from Nginx. That’s because it’s nothing here: http://127.0.0.1:8000

Now, configure the website to run on 8000 port.

Configuring Gunicorn

Are you guys alive? Don’t give up, we’re almost there.

In your virtualenv (remember workon name_env?) install Gunicorn

pip install gunicorn

In your project’s directory, make a gunicorn_conf file:

bind = "127.0.0.1:8000"
logfile = "/home/username/logs/gunicorn.log"
workers = 3

Now, if you run Gunicorn you will see your website working!

/home/username/Envs/name_venv/bin/gunicorn project.wsgi:application -c gunicorn_conf

But what are you going to do? Run this command inside a screen and walk away? Of course not! You’ll use Supervisord to control Gunicorn.

Configuring Supervisor

Now create a gunicorn.conf:

sudo vi /etc/supervisor/conf.d/gunicorn.conf

That’s the content:

[program:gunicorn]
command=/home/username/Envs/name_venv/bin/gunicorn project.wsgi:application -c /home/username/project/project_django/gunicorn_conf
directory=/home/username/project/project-django
user=username
autostart=true
autorestart=true
redirect_stderr=true

And now, you just tell Supervisor that there is a new process in town and Supervisord will take care of it:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart gunicorn

And voilá! A new running you will have.

Conclusion

There is a lot of things involved in a deploy process. You have to configure a firewall, probably you’ll have to serve more than one static folder, etc, etc… But you have to start somewhere.

I can’t believe I wrote a whole post without using any GIF. So, just to finish, pay attention to all paths I’ve used here.

Oops..

Pip Installing a Package From a Private Repository

That’s a python quick tip. It’s very basic, but still very helpful. When your company uses GitHub for private repositories you often want to put them on the requirements.

First of all, remember to add your public key on your GitHub settings.

 

You just have to use like this:

pip install git+ssh://[email protected]/<<your organization>>/<<the project>>[email protected]<< the tag>>

You can even use on your requirements, without the pip install. E.g. if your organization is called Django and your project is called… let’s say… Django and you’d like to add Django 1.11.4 in your requirements you can use like this:

git+ssh://[email protected]/django/[email protected]

Probably you already have a deploy key configured to your server or a machine user, it will work for your private Repos on your server, if you don’t, take a look at this

 

SSH Keys

If you don’t know how to generate your ssh-key, It’s easy:

cd ~/.ssh
ssh-keygen -t rsa -b 4096 -C "[email protected]"
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa

Now, copy the content of your public key (id_rsa.pub).

cat ~/.ssh/id_rsa.pub

In your GitHub account go to  Settings > SSH and GPG Keys and add it.

Como criar um programa com Auto-Update utilizando Python e Esky

Essa é a segunda parte da série de Posts Distribuir Programas em Python para Desktops Windows. Se quiser, veja o primeiro post: Como criar um executável com Instalador MSI em Python

Sempre que um programa precisa ser atualizado parece um parto. Lembre-se de todas as vezes você teve que baixar a versão atualizada do Java, lembra bem dessa raiva que você sentiu ao ter que fazer isso e imagine que seu usuário sinta a mesma raiva que você, então seja legal e faça um auto-update no seu programa para que ninguém precise baixar as versões novas. Para aprender como criar um programa com Auto-Update utilizando Python e Esky eu vou usar o aplicativo boneca criado na parte 1.

Até aqui tudo normal, o programa está feito, foi compilado mas não tem auto-update, você simplesmente gerou um instalador da boneca e nunca mais vai atualizar esse programa no cliente que o instalou, então você precisa fazer alterações e gerar um novo executável.

Bom, vamos ter que instalar um package chamado Esky:

pip install esky

Agora vamos fazer alterações no código, primeiro no script da Boneca.py precisamos importar o esky e pedir pra ele procurar por atualizações na internet quando estiverem disponíveis.

#Coloque abaixo de import win32con
import esky

if hasattr(sys,"frozen"):
    app = esky.Esky(sys.executable,"http://teenspirit.com.br/exemplo_boneca/")
    app.auto_update()

Toda vez que você iniciar o programa ele vai procurar no site se tem alguma atualização pra baixar. Ele conseguirá reconhecer pelo número da versão. Eu simplesmente deixei esse diretório (http://teenspirit.com.br/exemplo_boneca/) pra fazer listagem no Apache.

Agora vamos fazer alterações no programa, eu vou colocar outra imagem no lugar da boneca, nesse caso, usarei o chuck.jpg

chuck


#Troque o texto de boneca.jpg para chuck.jpg
os.startfile(os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),"chuck.jpg"))

 

Agora, vamos alterar o setup.py:

#setup.py
import esky.bdist_esky
from esky.bdist_esky import Executable as Executable_Esky
from cx_Freeze import setup, Executable

include_files = ['boneca.jpg','chuck.jpg']

setup(
    name = 'boneca',
    version = '1.0.1',
    options = {
        'build_exe': {
            'packages': ['os','sys','ctypes','win32con'],
            'excludes': ['tkinter','tcl','ttk'],
            'include_files': include_files,
            'include_msvcr': True,
        },
        'bdist_esky': {
            'freezer_module': 'cx_freeze',
        }
    },
    data_files = include_files,
    scripts = [
        Executable_Esky(
            "boneca.py",
            gui_only = True,
            #icon = XPTO #Coloque um icone aqui se quiser ,
            ),
    ],
    executables = [Executable('boneca.py',base='Win32GUI')]
    )

Reparem que eu mudei o número da Versão para 1.0.1, e a partir dessa versão o programa terá o auto-update. Esse arquivo ficou bem diferente, vou tentar explicar tudo que aconteceu nele:

1. Importei a opção de setup bdist_esky e um tipo de executável do próprio Esky

import esky.bdist_esky
from esky.bdist_esky import Executable as Executable_Esky

2. Coloquei as opções para o novo argumento bdist_esky:

        'bdist_esky': {
            'freezer_module': 'cx_freeze',
        }

3. Adicionei o data_files, porquê é isso que o Esky usa para ver quais arquivos também precisam ir para a pasta do programa

      data_files = include_files,

4. Coloquei o scripts porque é daí que o Esky vê quais scripts se tornaram os executáveis.

    scripts = [
        Executable_Esky(
            "boneca.py",
            gui_only = True,
            #icon = XPTO #Coloque um icone aqui se quiser ,
            ),
    ],

Agora para executar você precisa usar o setup.py com o argumento bdist_esky

python setup.py bdist_esky

Ele vai gerar um arquivo compactado que vai ficar na sua pasta dist:

boneca_esky
E dentro desse arquivo você tem isso aqui:

boneca-esky-conteudo

 

O que aconteceu? O Esky criou um programa, boneca.exe que vai ser responsável pela atualização do seu programa, inclusive dele mesmo. Quando ele for aberto ele vai buscar no endereço que eu coloquei (http://teenspirit.com.br/exemplo_boneca/) e se achar um outro arquivo compactado com uma versão mais recente ele vai baixar o arquivo e trocar o conteúdo, então na próxima vez que ele for aberto ele abrirá com a versão mais nova e limpará a versão mais antiga.

O bom é que se der algum problema no pacote, ou na internet ou na energia o Esky gerencia isso para que o programa sempre funcione. (só não pode subir uma versão bichada).

Então, quem de agora em diante for baixar o seu programa terá que baixar essa versão do Esky, que tem o arquivo zipado, aquele que geramos na Parte 1 não tem auto-update. E é por isso que vou mostrar na Parte 3 como fazer uma distribuição bem mais legal com o instalador do Inno Setup. Outra coisa que não é muito legal é que essa versão que criamos não tem o suporte ao MSVCR, mais um motivo para você ter um instalador customizado que vai incluir isso pra você.

Pra testar a atualização eu vou criar mais uma nova versão, que vou chamar de 1.0.2, nela vou trocar a foto, para isso mudo o boneca.py:

os.startfile(os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),"seu-boneco.jpg"))

e vou adicionar esse novo arquivo seu-boneco.jpg no include_files do setup.py:

include_files = ['boneca.jpg','chuck.jpg', 'seu-boneco.jpg']

Agora é só gerar o novo arquivo do Esky:

python setup.py bdist_esky

Na sua pasta dist vai aparecer o arquivo boneca-1.0.2.win32.zip. É só colocá-lo no link informado e na próxima vez que seus usuários abrirem o programa, ele será atualizado.

Se você quiser testar esse programa, faça o seguinte: baixe o boneca-1.0.1.win32.zip, descompacte o arquivo em uma pasta e abra o boneca.exe .

Quando você apertar o Print Screen ele vai mostrar a foto do Chuck, mas perceba que ele também já estará fazendo a atualização para a versão 1.0.2 e depois de fechado, na próxima vez que o programa rodar ele exibirá a foto do Seu Boneco.

seu-boneco

 

O código continua no Github.

E segura que em breve posto a Parte 3.

Projeto Django privado para código aberto sem comprometer a segurança

Uma vez resolvi passar um projeto django que fiz de fechado para aberto e acabei colocando as senhas que usava no servidor de emails no GitHub. É mole?

Bom, se o projeto é novo recomendo o seguinte, nunca coloque dados secretos no seu settings.py e dentro do git, inclusive o SECRET_KEY gerado.faça o seguinte, crie um arquivo settings_local.py na mesma pasta que o seu settings.py

Neste arquivo coloque toda a informação que você não quer que seja compartilhada, como por exemplo:

  • SECRET_KEY
  • EMAIL_HOST_PASSWORD (E outras configurações de Email)
  • SOCIAL TOKENS (como para Facebook e Twitter)
  • etc…

No final do seu arquivo settings.py importe tudo que estiver no settings_local.py:

 try:
     from settings_local import *
 except ImportError:
     pass

Agora você precisa adicionar o settings_local no .gitignore para que ele não seja enviado durante seus commits. Se você ainda não criou o arquivo faça o seguinte:

touch .gitignore
vim .gitignore

Dentro desse arquivo coloque o caminho relativo do seu settings.py, no caso <nome_do_app>/settings_local.py e depois adicione seu gitignore no git.

git add .gitignore
git commit -m "Ignorando o settings local"

Pronto, agora você consegue continuar programando sem se preocupar em colocar dados que não deveria em um projeto OpenSource.

Mas e se você já estava fazendo tudo sem isso e o resto dos dados estão todos no histórico do GIT? Você pode usar o BFG Repo-Cleaner ou o git-filter-branch.