Heroku é atualmente uma das melhores opções PaaS ( Plataform as a Service ) para desenvolvedores fazerem o deploy de suas aplicações, essa plataforma é muito utilizada nas startups pois utilizando ela não é necessário se preocupar com gerenciamento de infraestrutura, monitoração de disco, cpu, memória e entre outros.
Nesse post vou mostrar como eu utilizo dessa plataforma para fazer o deploy dos meus projetos pessoais em ambientes production-like.
O código fonte da aplicação está publicado no link abaixo:
https://github.com/Responsus/TerminusNG
Essa aplicação é uma versão um pouco melhorada do meu tcc da faculdade, que foi um sistema para gerenciamento de projetos, inicialmente esse sistema foi escrito utilizando um framework chamado Pyramid e nessa nova versão foi escrita utilizando o microframework Flask.
O Heroku possui vários planos que podem ser usados, eu uso o plano free, que disponibiliza um container com apenas 1 Worker e desliga após 30 minutos de inatividade, que é o suficiente para testar aplicações em ambientes não produção.
Todos os planos estão disponíveis no link abaixo:
https://www.heroku.com/pricing
Agora, o primeiro passo é criar uma conta no Heroku.
https://signup.heroku.com/login
Após a sua conta criada, crie um novo app, clicando no botão New no topo da página conforme a imagem abaixo:
Ao criar o novo app, dê um nome único a ele.
Após a criação do seu app, você será redirecionado para uma outra página e nela clique em Settings, logo abaixo você encontrará um link para o seu repositório no Heroku, salve esse link que será usado mais a frente.
Agora é necessário instalar o heroku-cli na máquina para publicar o projeto.
Nesse link temos todos os links para download em vários sistemas operacionais.
https://devcenter.heroku.com/articles/heroku-cli
Eu instalei a versão para windows, então os exemplos que vou usar em diante serão digitados usando o Windows Powershell.
Abaixo uma lista dos comandos disponíveis para o Heroku-cli.
PS C:\Users\1511 MXTI\Documents> heroku --help
Usage: heroku COMMAND
Help topics, type heroku help TOPIC for more details:
access manage user access to apps
addons tools and services for developing, extending, and operating your app
apps manage apps
auth heroku authentication
authorizations OAuth authorizations
buildpacks manage the buildpacks for an app
certs a topic for the ssl plugin
ci run an application test suite on Heroku
clients OAuth clients on the platform
config manage app config vars
container Use containers to build and deploy Heroku apps
domains manage the domains for an app
drains list all log drains
features manage optional features
git manage local git repository for app
keys manage ssh keys
labs experimental features
local run heroku app locally
logs display recent log output
maintenance manage maintenance mode for an app
members manage organization members
notifications display notifications
orgs manage organizations
pg manage postgresql databases
pipelines manage collections of apps in pipelines
plugins manage plugins
ps manage dynos (dynos, workers)
redis manage heroku redis instances
regions list available regions
releases manage app releases
run run a one-off process inside a Heroku dyno
sessions OAuth sessions
spaces manage heroku private spaces
status status of the Heroku platform
teams manage teams
update update CLI
webhooks setup HTTP notifications of app activity
Primeiro vou fazer o git clone da aplicação.
PS C:\Users\1511 MXTI> git clone https://github.com/Responsus/TerminusNG.git
Cloning into 'TerminusNG'...
remote: Counting objects: 816, done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 816 (delta 14), reused 21 (delta 10), pack-reused 783
Receiving objects: 100% (816/816), 4.21 MiB | 422.00 KiB/s, done.
Resolving deltas: 100% (130/130), done.
PS C:\Users\1511 MXTI>
Com a aplicação já clonada, entrei no diretório e fiz o login do heroku.
PS C:\Users\1511 MXTI> cd .\TerminusNG\
PS C:\Users\1511 MXTI\TerminusNG> heroku login
Enter your Heroku credentials:
Email: alisson.copyleft@gmail.com
Password: *****************
Logged in as alisson.copyleft@gmail.com
PS C:\Users\1511 MXTI\TerminusNG>
Agora adicione o repositório copiado do heroku nos passos anteriores.
PS C:\Users\1511 MXTI\TerminusNG> git remote add heroku https://git.heroku.com/alissonmachado.git
PS C:\Users\1511 MXTI\TerminusNG>
Após adicionado o heroku como remote, foi feito um push para esse repositório:
PS C:\Users\1511 MXTI\TerminusNG> git add --all
PS C:\Users\1511 MXTI\TerminusNG> git commit -m "deploy heroku"
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
PS C:\Users\1511 MXTI\TerminusNG> git push heroku
Counting objects: 816, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (645/645), done.
Writing objects: 100% (816/816), 4.21 MiB | 59.00 KiB/s, done.
Total 816 (delta 130), reused 816 (delta 130)
A saida do comando será parecida com essa:
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Python app detected
remote: -----> Installing python-3.6.4
remote: -----> Installing pip
remote: -----> Installing requirements with pip
remote: Collecting Flask (from -r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 1))
remote: Downloading Flask-0.12.2-py2.py3-none-any.whl (83kB)
remote: Collecting Flask_SQLAlchemy (from -r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 2))
remote: Downloading Flask_SQLAlchemy-2.3.2-py2.py3-none-any.whl
remote: Collecting gunicorn (from -r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 3))
remote: Downloading gunicorn-19.7.1-py2.py3-none-any.whl (111kB)
remote: Collecting flask-security (from -r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading Flask_Security-3.0.0-py2.py3-none-any.whl (68kB)
remote: Collecting flask-mongoengine (from -r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 5))
remote: Downloading flask-mongoengine-0.9.3.tar.gz (111kB)
remote: Collecting bcrypt (from -r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 6))
remote: Downloading bcrypt-3.1.4-cp36-cp36m-manylinux1_x86_64.whl (54kB)
remote: Collecting itsdangerous>=0.21 (from Flask->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 1))
remote: Downloading itsdangerous-0.24.tar.gz (46kB)
remote: Collecting click>=2.0 (from Flask->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 1))
remote: Downloading click-6.7-py2.py3-none-any.whl (71kB)
remote: Collecting Jinja2>=2.4 (from Flask->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 1))
remote: Downloading Jinja2-2.10-py2.py3-none-any.whl (126kB)
remote: Collecting Werkzeug>=0.7 (from Flask->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 1))
remote: Downloading Werkzeug-0.14.1-py2.py3-none-any.whl (322kB)
remote: Collecting SQLAlchemy>=0.8.0 (from Flask_SQLAlchemy->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 2))
remote: Downloading SQLAlchemy-1.2.0.tar.gz (5.5MB)
remote: Collecting passlib>=1.7 (from flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading passlib-1.7.1-py2.py3-none-any.whl (498kB)
remote: Collecting Flask-Login>=0.3.0 (from flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading Flask-Login-0.4.1.tar.gz
remote: Collecting Flask-Mail>=0.7.3 (from flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading Flask-Mail-0.9.1.tar.gz (45kB)
remote: Collecting Flask-Principal>=0.3.3 (from flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading Flask-Principal-0.4.0.tar.gz
remote: Collecting Flask-BabelEx>=0.9.3 (from flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading Flask-BabelEx-0.9.3.tar.gz (41kB)
remote: Collecting Flask-WTF>=0.13.1 (from flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading Flask_WTF-0.14.2-py2.py3-none-any.whl
remote: Collecting mongoengine>=0.8.0 (from flask-mongoengine->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 5))
remote: Downloading mongoengine-0.15.0.tar.gz (144kB)
remote: Collecting six (from flask-mongoengine->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 5))
remote: Downloading six-1.11.0-py2.py3-none-any.whl
remote: Collecting cffi>=1.1 (from bcrypt->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 6))
remote: Downloading cffi-1.11.3-cp36-cp36m-manylinux1_x86_64.whl (420kB)
remote: Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->Flask->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 1))
remote: Downloading MarkupSafe-1.0.tar.gz
remote: Collecting blinker (from Flask-Mail>=0.7.3->flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading blinker-1.4.tar.gz (111kB)
remote: Collecting Babel>=1.0 (from Flask-BabelEx>=0.9.3->flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading Babel-2.5.1-py2.py3-none-any.whl (6.8MB)
remote: Collecting speaklater>=1.2 (from Flask-BabelEx>=0.9.3->flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading speaklater-1.3.tar.gz
remote: Collecting WTForms (from Flask-WTF>=0.13.1->flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading WTForms-2.1.zip (553kB)
remote: Collecting pymongo>=2.7.1 (from mongoengine>=0.8.0->flask-mongoengine->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 5))
remote: Downloading pymongo-3.6.0-cp36-cp36m-manylinux1_x86_64.whl (378kB)
remote: Collecting pycparser (from cffi>=1.1->bcrypt->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 6))
remote: Downloading pycparser-2.18.tar.gz (245kB)
remote: Collecting pytz>=0a (from Babel>=1.0->Flask-BabelEx>=0.9.3->flask-security->-r /tmp/build_f5fec0d0c01ac466add1d2bf054ac08c/requirements.txt (line 4))
remote: Downloading pytz-2017.3-py2.py3-none-any.whl (511kB)
remote: Installing collected packages: itsdangerous, click, MarkupSafe, Jinja2, Werkzeug, Flask, SQLAlchemy, Flask-SQLAlchemy, gunicorn, passlib, Flask-Login, blinker, Flask-Mail, Flask-Principal, pytz, Babel, speaklater, Flask-BabelEx, WTForms, Flask-WTF, flask-security, pymongo, six, mongoengine, flask-mongoengine, pycparser, cffi, bcrypt
remote: Running setup.py install for itsdangerous: started
remote: Running setup.py install for itsdangerous: finished with status 'done'
remote: Running setup.py install for MarkupSafe: started
remote: Running setup.py install for MarkupSafe: finished with status 'done'
remote: Running setup.py install for SQLAlchemy: started
remote: Running setup.py install for SQLAlchemy: finished with status 'done'
remote: Running setup.py install for Flask-Login: started
remote: Running setup.py install for Flask-Login: finished with status 'done'
remote: Running setup.py install for blinker: started
remote: Running setup.py install for blinker: finished with status 'done'
remote: Running setup.py install for Flask-Mail: started
remote: Running setup.py install for Flask-Mail: finished with status 'done'
remote: Running setup.py install for Flask-Principal: started
remote: Running setup.py install for Flask-Principal: finished with status 'done'
remote: Running setup.py install for speaklater: started
remote: Running setup.py install for speaklater: finished with status 'done'
remote: Running setup.py install for Flask-BabelEx: started
remote: Running setup.py install for Flask-BabelEx: finished with status 'done'
remote: Running setup.py install for WTForms: started
remote: Running setup.py install for WTForms: finished with status 'done'
remote: Running setup.py install for mongoengine: started
remote: Running setup.py install for mongoengine: finished with status 'done'
remote: Running setup.py install for flask-mongoengine: started
remote: Running setup.py install for flask-mongoengine: finished with status 'done'
remote: Running setup.py install for pycparser: started
remote: Running setup.py install for pycparser: finished with status 'done'
remote: Successfully installed Babel-2.5.1 Flask-0.12.2 Flask-BabelEx-0.9.3 Flask-Login-0.4.1 Flask-Mail-0.9.1 Flask-Principal-0.4.0 Flask-SQLAlchemy-2.3.2 Flask-WTF-0.14.2 Jinja2-2.10 MarkupSafe-1.0 SQLAlchemy-1.2.0 WTForms-2.1 Werkzeug-0.14.1 bcrypt-3.1.4 blinker-1.4 cffi-1.11.3 click-6.7 flask-mongoengine-0.9.3 flask-security-3.0.0 gunicorn-19.7.1 itsdangerous-0.24 mongoengine-0.15.0 passlib-1.7.1 pycparser-2.18 pymongo-3.6.0 pytz-2017.3 six-1.11.0 speaklater-1.3
remote:
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 58.4M
remote: -----> Launching...
remote: Released v3
O log acima, mostra todo o processo de instalação de dependências para que seja criado o container e seja feito o deploy da aplicação, mas o mais importante é o final do log que aparecerá similiar ao mostrado abaixo:
remote: -----> Launching...
remote: Released v3
remote: https://alissonmachado.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
Foi retornado a seguinte url: https://alissonmachado.herokuapp.com/.
E pronto, agora é só acessar a aplicação que já estará funcionando.
Mas como foi feito o deploy?
Ao se fazer uma aplicação que será publicada no heroku, ela já deve ser construída pensada em heroku, assim como qualquer outra plataforma DevOps que faz o deploy em containers.
Dentro do repositório existe um arquivo chamado Procfile: https://github.com/Responsus/TerminusNG/blob/master/Procfile , nesse arquivo é definido qual é o processo que deve ser iniciado, que no nosso caso é o servidor Web gunicorn e ele vai iniciar p arquivo app.py que é o principal da aplicação.
Outro ponto importante é deixar o arquivo requirements.txt: https://github.com/Responsus/TerminusNG/blob/master/requirements.txt, sempre atualizado, pois ele também faz a instalação do gunicorn que é o servidor de aplicação que é usado durante o deploy.
E acho que é isso ai, qualquer coisa é só mandar um salve (=