7.3 Implantação automática no Heroku
Vamos começar esta seção já colocando a mão na massa!
Comece entrando no Heroku e criando duas aplicações. Uma delas vai ser o ambiente staging, para testes e experimentação, e outra vai ser o ambiente de produção. Escolha nomes únicos, como no exemplo da figura:

Temos portanto duas aplicações, e iremos configurar o nosso pipeline de CI/CD da seguinte maneira:
cadastro-produto-dlucredio-stg
: ambiente de staging. Sempre que houver um commit no branch chamado main, será construída uma imagem e enviada para essa aplicação.cadastro-produto-dlucredio-prd
: ambiente de produção. Será configurado um job para envio da imagem para essa aplicação, mas ele não será automático, e sim manual. A ideia é que alguém, manualmente, decida por fazer o deploy, possivelmente depois que a versão de staging tiver sido testada exaustivamente. A princípio, não é muito diferente do que já fizemos anteriormente, porém aqui há a facilidade de estar tudo configurado no GitLab, ou seja, não há a necessidade sequer de haver o Docker instalado na máquina do desenvolvedor!
Vamos começar!
Obtendo tokens de acesso para o Heroku
Acessando o seu perfil no Heroku (canto superior direito), clique em "Account settings", e depois encontre "API Key". Clique em "Reveal". Copie e cole esse valor.

Agora vá até o GitLab, entre no seu projeto, e selecione a opção "Settings" -> "CI/CD" -> "Variables". Já adicionamos 3 variáveis lá, que são os dados de acesso ao Docker Hub. Fizemos isso na seção anterior. Adicione mais três:
HEROKU_API_KEY
: o valor é a chave que acabamos de copiar e colarHEROKU_STAGING_APP
: coloque o nome do aplicativo de stagingHEROKU_PRODUCTION_APP
: coloque o nome do aplicativo de produção
Ao final, devemos ter seis variáveis configuradas. Todas elas estão disponíveis para os scripts de CI/CD:

Agora vamos modificar o arquivo de pipeline .gitlab-ci.yml
:
default:
image: python:3.10.2-slim
stages:
- verify
- quality
- publish
+ - deploy
variables:
DOCKER_TLS_CERTDIR: ""
verify-python:
stage: verify
script:
- python --version
- whoami
verify-pip:
stage: verify
script:
- pip install -r requirements.txt
run_tests:
stage: quality
script:
- pip install -r requirements.txt
- python -m unittest
analyze_coverage:
stage: quality
script:
- pip install -r requirements.txt
- coverage run -m unittest
- coverage report --fail-under=100
deploy_docker_hub:
image: docker:28.1.1
stage: publish
services:
- docker:28.1.1-dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
script:
- docker build --pull -t "$CI_REGISTRY_IMAGE" .
- docker push "$CI_REGISTRY_IMAGE"
only:
- main
+deploy_heroku_staging:
+ image: docker:28.1.1
+ stage: deploy
+ services:
+ - docker:28.1.1-dind
+ before_script:
+ - apk add --no-cache curl
+ - apk add --no-cache bash
+ - apk add --no-cache nodejs
+ - curl https://cli-assets.heroku.com/install.sh | sh
+ - docker login --username=_ -p "$HEROKU_API_KEY" registry.heroku.com
+ script:
+ - heroku stack:set container --app $HEROKU_STAGING_APP
+ - heroku container:push web --app $HEROKU_STAGING_APP
+ - heroku container:release web --app $HEROKU_STAGING_APP
+ only:
+ - main
+deploy_heroku_production:
+ image: docker:28.1.1
+ stage: deploy
+ services:
+ - docker:28.1.1-dind
+ before_script:
+ - apk add --no-cache curl
+ - apk add --no-cache bash
+ - apk add --no-cache nodejs
+ - curl https://cli-assets.heroku.com/install.sh | sh
+ - docker login --username=_ -p "$HEROKU_API_KEY" registry.heroku.com
+ script:
+ - heroku stack:set container --app $HEROKU_PRODUCTION_APP
+ - heroku container:push web --app $HEROKU_PRODUCTION_APP
+ - heroku container:release web --app $HEROKU_PRODUCTION_APP
+ when: manual
+ only:
+ - main
Há dois novos jobs, um para implantação no ambiente de staging e outro para implantação no ambiente de produção. Note como, para o ambiente de staging, o job está configurado para rodar automaticamente assim que houver um commit no branch main (configuração only: main
). Isso significa que alterações em outros branches não provocam um deploy automático.
Já o job para implantação no ambiente de produção foi configurado when: manual
. Isso significa que esse job não irá rodar automaticamente em hipótese alguma. Ou seja, é necessário que alguém vá lá e dispare sua execução para efetuar o deploy.
Ambos os jobs utilizam um preâmbulo (before_script
) que faz o seguinte:
instalar alguns pacotes auxiliares (curl, bash e nodejs)
instalar a CLI do Heroku via curl
login no registro de contêineres do heroku
Em seguida os jobs fazem a construção e envio da imagem, e seu release. Compare esses comandos com aqueles que vimos no final da Seção 3.3. São exatamente os mesmos, portanto nenhuma novidade aqui!
Estamos quase prontos para enviar as alterações. Porém, antes, precisamos alterar duas coisas.
Primeiro, como já fizemos antes no final da Seção 3.3, é preciso deixar a porta variável, pois o Heroku define a porta dinamicamente. Altere o Dockerfile
para não mais exportar a porta 80 (o Heroku fará isso com uma porta aleatória):
-EXPOSE 80/tcp
+#EXPOSE 80/tcp
E também altere o arquivo default
, para que o nginx não tenha uma porta fixa:
-listen 80;
+listen $PORT;
E por último, o arquivo entrypoint.sh
, para substituir dinamicamente (na hora de subir o contêiner) o valor de $PORT
no default
pelo valor sorteado pelo Heroku e que será informado ao contêiner como uma variável de ambiente:
#!/bin/bash
gunicorn -w 4 -b 127.0.0.1:5000 app:app &
-nginx -g 'daemon off;' &
+sed -i -e 's/$PORT/'"$PORT"'/g' /etc/nginx/sites-available/default && nginx -g 'daemon off;' &
wait -n
exit $?
Isso nós já tínhamos feito lá na Seção 3.3.
A última mudança é a seguinte: como agora teremos dois ambientes diferentes, seria bom se, na página cadastro.html
de cada versão (staging e produção), a requisição à API HTTP fosse direcionada à sua respectiva versão. Até agora estivemos usando um endereço fixo no comando fetch
. É hora de mudarmos isso. Altere a linha do arquivo cadastro.html
da seguinte forma:
- fetch("http://127.0.0.1:8080/api/predizer_categoria", {
+ fetch("api/predizer_categoria", {
Com isso, o navegador irá automaticamente fazer a requisição de maneira relativa ao local onde a página está hospedada. Dessa forma, o mesmo código Javascript irá funcionar em qualquer ambiente, basta que a API esteja definida embaixo da URL /api
, que foi o que fizemos no nginx.
Pronto, vamos enviar as mudanças:
git commit -am "Deploy no Heroku via CI/CD"
git push
Aguarde até que o pipeline seja bem sucedido:

Repare que apenas o deploy no ambiente de staging foi executado. O outro foi configurado para ser executado manualmente. Faça isso agora, clicando no símbolo de "play" ao lado do job. Aguarde até que o mesmo execute corretamente e vamos checar lá no Heroku para ver se deu tudo certo:

Sim, na figura acima podemos ver que o serviço web ./entrypoint.sh
está "ON". Agora vamos testar. No dashboard do Heroku, procure pelo botão "Open app", que fica no canto da tela, para abrir a aplicação. A URL será mais ou menos assim:
https://cadastro-produto-dlucredio-stg-f9f1daf3b085.herokuapp.com/
https://cadastro-produto-dlucredio-prd-0bba304bafc1.herokuapp.com/
Se estiver tudo funcionando, significa que conseguimos completar o ciclo de DevOps!
Last updated