Deploy de Phoenix no Heroku

Carlos Souza
September 17, 2021

Prestes a completar 15 anos ūüéā, o Heroku continua sendo a minha escolha preferida para publica√ß√£o r√°pida de aplica√ß√Ķes na nuvem. Neste artigo, iremos aprender como publicar uma aplica√ß√£o Elixir/Phoenix em sua infraestrutura utilizando buildpacks.

Antes de mais nada, é preciso entender alguns termos essenciais do Heroku:

  1. Dynos
  2. Slugs
  3. Buildpacks

Estes s√£o os nomes dados pelo Heroku para alguns conceitos relacionados a tecnologia de containers.

Dynos

O Heroku utiliza containers para todas as aplica√ß√Ķes publicadas e rodando em sua PAAS. Containers no Heroku s√£o chamados de dynos. Podemos escolher entre diferentes tipos de dynos, cada um com uma determinada configura√ß√£o. Existe um tipo gratuito e que costuma ser suficiente para experimenta√ß√£o e testes. O maior inconveniente deste tipo √© o fato do dyno ser desligado ap√≥s 30 minutos de inatividade, e o tempo de startup do primeiro request para ‚Äúacordar‚ÄĚ o dyno costuma demorar alguns segundos.

Slugs

Ap√≥s dado um git push para o Heroku, as aplica√ß√Ķes s√£o transformadas em um artefato otimizado para rodar em um dyno. Este artefato √© chamado de slug, e o respons√°vel por esta transforma√ß√£o √© o slug compiler.

Buildpacks

O slug compiler executa um conjunto de scripts. Estes scripts s√£o conhecidos como buildpacks. Os buildpacks variam de acordo com a linguagem de programa√ß√£o, e uma aplica√ß√£o pode utilizar mais do que um buildpack ‚ÄĒ como no caso de uma aplica√ß√£o web Phoenix.

Realizando o Deployment

A partir de uma aplica√ß√£o Phoenix rodando localmente e comunicando-se com um banco de dados, os seguintes passos s√£o executados at√© que a aplica√ß√£o esteja publicada no Heroku e dispon√≠vel para acesso p√ļblico:

1- Criação de um dyno

2- Configuração de buildpacks

3- Configuração de ENVs e secrets

4- Comando de deploy (spoiler: é o git push)

O código fonte completo da aplicação de exemplo que usaremos neste post está disponível neste link.

Criação de um dyno

A partir da raíz do projeto, rodamos o comando a seguir

heroku create --buildpack hashnuke/elixir

Este comando cria um novo dyno e j√° de imediato adiciona o buildpack de Elixir para as configura√ß√Ķes de runtime. Caso a aplica√ß√£o esteja versionada com o git, o novo remote √© automaticamente adicionado √†s configura√ß√Ķes locais. Ap√≥s a conclus√£o do comando, o comando git remote -v dever√° listar, al√©m do origin, um remote de nome heroku.

Configura√ß√Ķes de buildpacks

√Č recomendado que as vers√Ķes de Elixir e Erlang utilizadas pela aplica√ß√£o sejam declaradas de forma expl√≠cita. Isto √© feito atrav√©s do arquivo elixir_buildpack.config. Neste arquivo, localizado na ra√≠z do projeto, definimos estas vers√Ķes e mais uma configura√ß√£o adicional com as linhas a seguir:

# elixir_buildpack.config
erlang_version=21.2.5
elixir_version=1.8.1
always_rebuild=true

A op√ß√£o always_rebuild=true garante que as depend√™ncias sejam re-compiladas a cada deploy. Isso aumenta um pouco o tempo de deploy, mas por outro lado, ajuda a evitar erros oriundos de vers√Ķes antigas de depend√™ncias.

Para que uma aplicação Phoenix rode com sucesso, alguns passos extras são necessários envolvendo a compilação dos assets e a inicialização do servidor de aplicação. Para nossa sorte, a simples adição do buildpack Phoenix Static resolve esta questão.

O comando a seguir instala o buildpack no nosso dyno:

heroku buildpacks:add https://github.com/gjaldon/heroku-buildpack-phoenix-static.git

Como fizemos para as vers√Ķes de Erlang e Elixir utilizadas pelo buildpack anterior, √© recomendado tamb√©m que sejamos expl√≠citos na vers√£o do Node.js utilizada por este novo buildpack. Crie um arquivo phoenix_static_buildpack.config na ra√≠z do projeto e adicione a linha a seguir a este arquivo:

# phoenix_static_buildpack.config
node_version=10.20.1

Conclu√≠mos as configura√ß√Ķes de buildpacks necess√°rias.

Configuração de variáveis de ambiente e secrets

Alguns dos arquivos gerados pelo Phoenix precisam ser editados antes que a aplicação rode no Heroku. Os arquivos a serem editados são os seguintes:

  1. config/prod.exs
  2. config/prod.secret.exs
  3. lib/guitar_store_web/endpoint.ex

No arquivo config/prod.exs iremos declarar a URL da nossa aplica√ß√£o e o port utilizado para conex√Ķes seguras HTTPS. A URL da nossa aplica√ß√£o foi retornada pelo primeiro comando que rodamos ao criar o dyno, mas tamb√©m pode ser exibida atrav√©s do comando heroku info.

Copie o valor da URL sem o protocolo e substitua a linha deste trecho de código

url: [host: "example.com", port: 80],

com o código a seguir:

http: [port: {:system, "PORT"}],
url: [scheme: "https", host: "<valor-da-URL-sem-o-protocolo>", port: 443],
force_ssl: [rewrite_on: [:x_forwarded_proto]],

O trecho final de código deverá ficar da seguinte forma:

config :guitar_store, GuitarStoreWeb.Endpoint,
http: [port: {:system, "PORT"}],
url: [scheme: "https", host: "valor-da-URL-sem-o-protocolo>", port: 443],
force_ssl: [rewrite_on: [:x_forwarded_proto]],
cache_static_manifest: "priv/static/cache_manifest.json"

Para completar a configuração de SSL, abra o arquivo config/prod.secret.exs e descomente a linha # ssl: true. Feito isto, o trecho de código deverá ficar assim:

config :guitar_store, GuitarStore.Repo,
ssl: true,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

Finalmente, uma √ļltima modifica√ß√£o para adicionar suporte a Websockets. No arquivo lib/guitar_store/endpoint.ex, mude o seguinte trecho

socket "/socket", GuitarStoreWeb.UserSocket,
websocket: true,
longpoll: false

para o código a seguir:

socket "/socket", GuitarStoreWeb.UserSocket,
websocket: [timeout: 45_000],
longpoll: false

Isto ativa suporte a Websockets com um timeout de 45 segundos - 10 segundos a menos do que o timeout atual de 55 segundos do Heroku para inatividade em conex√Ķes.

Pronto?‚Ķ quase ;) Conclu√≠mos as modifica√ß√Ķes na aplica√ß√£o. Agora precisamos adicionar um banco de dados ao nosso dyno e configurar as vari√°veis de ambiente necess√°rias para que nossa aplica√ß√£o se conecte a ele. Precisamos tamb√©m configurar uma chave secreta ūüĒź, respons√°vel pela cria√ß√£o e prote√ß√£o das sess√Ķes dos usu√°rios conectados.

Execute os seguintes comandos para criar e configurar um banco de dados relacional no Heroku:

heroku addons:create heroku-postgresql:hobby-dev
heroku config:set POOL_SIZE=18

Para gerar uma chave secreta, podemos utilizar uma task do próprio Phoenix. Rode o seguinte comando:

mix phx.gen.secret

O resultado deste comando √© a nossa chave secreta ūüĒź. Copie este valor e o utilize no comando a seguir, que configura o valor da chave como vari√°vel de ambiente no nosso dyno:

heroku config:set SECRET_KEY_BASE="<valor-da-chave-secreta>"

Pronto! :) O próximo passo é publicar a nossa aplicação.

Comando de deploy

Para publicar nossa aplica√ß√£o, basta um git push para o reposit√≥rio criado pelo Heroku para a nossa aplica√ß√£o, e que foi adicionado √† nossa configura√ß√£o local. Crie um commit com as √ļltimas mudan√ßas:

git add .
git commit -m "Deploy to Heroku"

Verifique que todos os arquivos editados foram adicionados ao commit e que n√£o h√° arquivos staged ou untracked pelo git, e rode o comando:

git push heroku main

Se tudo der certo (ūü§ě), ap√≥s alguns instantes a aplica√ß√£o estar√° publicada!

O √ļltimo passo antes que possamos acessar nossa aplica√ß√£o √© a execu√ß√£o das migrations, criando as tabelas necess√°rias no banco de dados. Rode o comando a seguir:

heroku run "POOL_SIZE=2 mix ecto.migrate"

Agora está pronto de verdade! Para acessar a aplicação, visite a URL ou então rode o comando heroku open. Este comando abre o browser padrão e visita a nossa aplicação no endereço de produção.

Conclus√£o

Neste post, aprendemos como publicar aplica√ß√Ķes Phoenix no Heroku utilizando buildpacks. Al√©m desta estrat√©gia, tamb√©m √© poss√≠vel publicar aplica√ß√Ķes atrav√©s de um Dockerfile. A estrat√©gia com Dockerfile pode parecer um pouco mais trabalhosa, mas oferece maior controle sobre o processo de compila√ß√£o e gera√ß√£o de artefatos, o que pode valer a pena dependendo do seu caso.

Espero que este post tenha sido √ļtil e que possa ajudar voc√™ a publicar seus projetos Phoenix para o mundo! \o/

‚Äć