Como publicar uma aplicação Phoenix na VPS com release Elixir e Docker

Fala aí, beleza? Para seguir o guia oficial de releases do Phoenix com Docker numa VPS, você roda mix phx.gen.release --docker num projeto Phoenix 1.6.3 ou superior, e ele gera o Dockerfile, o .dockerignore e os scripts bin/server e bin/migrate
Tenha acesso root e SSD NVMe, com 10% OFF extra pelo link da Hora de Codar.
Ver a oferta de VPS Parceria Hora de CodarDepois é construir a imagem, que leva só a release (um diretório autocontido com a VM Erlang, o Elixir, o seu código e as dependências), e rodar o container na VPS passando SECRET_KEY_BASE, DATABASE_URL, PHX_HOST e PORT 🙂
O foco aqui NÃO é deploy genérico de container, é o artefato de release: o que entra nele, o que fica de fora e por que isso muda o jeito de configurar segredos e rodar migrações
Pra hospedar, a recomendação da Hora de Codar é a VPS da Hostinger, porque ela dá acesso root e oferece um template de sistema operacional com Docker pra aplicar pelo painel, o que facilita preparar o ambiente que esse fluxo pede (este artigo contém links de afiliado da Hostinger)
O que é uma release Phoenix e por que ela muda o deploy com Docker
A release é a sua aplicação empacotada num diretório autocontido, com a VM Erlang, o Elixir, todo o código e as dependências
Ela é montada com este comando e vai parar em _build/prod/rel/<app>:
MIX_ENV=prod mix release
E por que isso importa pro Docker? Se liga em duas consequências práticas
- Mesmo SO e mesma arquitetura: dá pra copiar o conteúdo de
_build/prod/rel/<app>e rodar em qualquer máquina de produção, desde que ela tenha o mesmo sistema operacional e a mesma arquitetura da máquina de build. O Docker ajuda a preparar o ambiente de build e execução, mas a imagem também precisa ser compatível com a arquitetura da VPS. Usar Debian nos dois estágios não elimina essa condição - O Mix não vai junto: a ferramenta de build fica de fora da release. Então nada de
mix ecto.migrateem produção, os comandos que você precisa (como as migrações) têm que estar DENTRO da release
Se você já fez um deploy de Astro com Docker, a ideia de container é a mesma, mas o que vai dentro da imagem é bem diferente: aqui não sobe runtime de linguagem com gerenciador de pacotes, sobe um artefato fechado
Pré-requisitos para publicar Phoenix com release e Docker na VPS
Antes de começar, confere essa lista:
- Projeto em Phoenix 1.6.3 ou superior: é a partir dessa versão que o
mix phx.gen.releaseexiste, pra gerar os arquivos de release e o Dockerfile - VPS com Ubuntu 22.04 LTS ou 24.04 LTS e pelo menos 2 GB de RAM: é o que o tutorial de Docker da Hostinger recomenda (o mínimo técnico citado ali é 512 MB, mas o recomendado é 2 GB)
- Acesso root ao servidor, pra instalar e administrar o Docker
- Docker no servidor: dá pra instalar manualmente no Ubuntu seguindo o tutorial da Hostinger, ou usar o template de sistema operacional com Docker disponível no hPanel, em VPS → Manage → OS & Panel → Operating System, na seção Change OS, buscando e selecionando o template Docker (segundo o tutorial deles, a instalação leva cerca de 10 minutos). Este guia pressupõe um servidor com Docker já preparado. Como recomendação de segurança, não aplique uma opção de Change OS em um servidor com aplicações e dados para seguir o exemplo; confira o procedimento do provedor e prepare backup antes de qualquer mudança de sistema
- PostgreSQL acessível pela aplicação, com a URL no formato
ecto://USER:PASS@HOST/DATABASE
Antes de aplicar esse template, vale dar uma olhada no tutorial oficial da Hostinger pra entender exatamente o que a troca de sistema operacional faz no servidor
Sobre a base do servidor: os planos de VPS da Hostinger (KVM 1, KVM 2, KVM 4 e KVM 8) usam processadores AMD EPYC e armazenamento SSD NVMe, o que é uma base coerente pra compilar a imagem e rodar a release na mesma máquina
Passo 1: gerar os arquivos de release e o Dockerfile com mix phx.gen.release --docker
Na raiz do projeto, roda:
mix phx.gen.release --docker
O resultado esperado são estes arquivos:
lib/app_name/release.ex: módulo com as tarefas de migração (só é gerado quando o projeto usa Ecto)rel/overlays/bin/migrateerel/overlays/bin/migrate.bat: scripts que rodam as migrações (o gerador cria os dois arquivos independente do sistema operacional em que você roda o comando; o.baté pensado pra quem for operar em Windows)rel/overlays/bin/servererel/overlays/bin/server.bat: scripts que iniciam a release com o servidor Phoenix ligado via variável de ambiente (de novo, os dois saem juntos, o.baté só pra ambiente Windows)Dockerfile: pronto pra qualquer deploy Docker padrão.dockerignore: com os padrões de Elixir
Mais controle no próximo deploy.
Configure sua aplicação em uma VPS Hostinger com acesso root. Escolha os recursos conforme o que seu projeto precisa.
- SSD NVMe
- AMD EPYC
- Backups semanais
Se o seu projeto não usa Ecto, os arquivos de migração simplesmente não aparecem, e tudo bem
Quer outra versão de Erlang/OTP ou de Elixir nas imagens base? O gerador tem as opções otp e elixir pra sobrescrever essas versões, aí vale confirmar a sintaxe exata da flag na documentação oficial do phx.gen.release antes de usar
Checagem: confere se os arquivos da lista apareceram no projeto antes de seguir, lembrando que release.ex, bin/migrate e migrate.bat só existem se o projeto usa Ecto
O erro comum deste passo: se o projeto usa npm ou yarn, o Dockerfile gerado não baixa esses pacotes sozinho
Você precisa adicionar manualmente um passo que instala as dependências JS, senão a compilação dos assets quebra no meio do build
Passo 2: entender o Dockerfile multi-stage e o que vai para a imagem de execução
O Dockerfile gerado tem dois estágios, os dois em Debian, e entender isso é o que separa esse deploy de um container genérico
Estágio builder: usa a imagem hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}
É ali que acontece a compilação, o build dos assets com MIX_ENV=prod mix assets.deploy e a montagem da release
Estágio final (chamado de final no template, também usa a imagem debian:${DEBIAN_VERSION}): pega SÓ a release pronta do builder
As linhas que fazem isso no template são estas:
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/<app> ./
USER nobody
CMD ["/app/bin/server"]
E por que isso é massa? A imagem final não carrega Mix nem toolchain de build, só o artefato
E o processo roda como nobody, ou seja, sem root dentro do container
O erro comum deste passo: tentar rodar mix dentro do container de produção
Não vai funcionar, o Mix ficou lá no estágio builder e não existe na release 😛
Passo 3: configurar as variáveis de ambiente lidas pelo config/runtime.exs
O config/runtime.exs roda depois da compilação e antes de o sistema iniciar, então os segredos entram como variáveis de ambiente na hora de subir o container, e não ficam gravados na imagem
As variáveis que importam aqui:
SECRET_KEY_BASE: assina e criptografa cookies e outros segredos. Gere commix phx.gen.secretDATABASE_URL: no formatoecto://USER:PASS@HOST/DATABASE. Oruntime.exsgerado levanta erro se ela não existir em produçãoPHX_HOST: o host usado no Endpoint (o padrão éexample.com)PORT: a porta do Endpoint (o padrão é4000)PHX_SERVER=true: liga o servidor web. Obin/serverjá define isso pra você
Pra gerar o segredo, na sua máquina de desenvolvimento:
mix phx.gen.secret
Um exemplo didático da lista de variáveis, com valores fictícios (troque tudo pelos seus):
SECRET_KEY_BASE=cole_aqui_o_valor_gerado_pelo_mix_phx_gen_secret
DATABASE_URL=ecto://app_user:[email protected]/minha_app_prod
PHX_HOST=meuapp.exemplo.com.br
PORT=4000
Guarde isso fora do repositório, beleza? Segredo commitado é dor de cabeça garantida

Crie sistemas com IA para vender
Prompts, modelos e checklists para criar e publicar seu sistema com IA e preparar a oferta para o primeiro cliente.
Quero o e-book gratuito Receba o livro e os bônus no seu e-mail.O erro comum deste passo: esquecer o PHX_HOST e deixar o padrão example.com
A variável existe justamente pra você apontar o Endpoint pro domínio real da aplicação, então vale confirmar que ela está definida com o host certo antes de ir pra produção
Passo 4: apontar a aplicação para um banco privado e rodar as migrações
Se o PostgreSQL roda numa rede privada, o host da DATABASE_URL aponta pro endereço interno do banco, e não pra um IP público
E se essa rede do banco for IPv6? Isso se configura no próprio config/runtime.exs: quando ECTO_IPV6 vem como true ou 1, o trecho acrescenta [:inet6] aos socket_options do Repo. O mix phx.gen.release não aplica isso automaticamente, ele só avisa no terminal pra você adicionar esse trecho quando ele ainda não existir no runtime.exs
Agora as migrações
Como não existe mix ecto.migrate na release, quem faz esse trabalho é o script bin/migrate, que chama as tarefas do módulo release.ex gerado no passo 1
É esse script que você executa no container da aplicação, com as mesmas variáveis de ambiente (principalmente a DATABASE_URL)
Pra não deixar a porta do banco aberta pra internet, use o gerenciamento de firewall da VPS e libere só o que a aplicação precisa expor
O erro comum deste passo: subir a aplicação sem DATABASE_URL
O runtime.exs levanta erro logo na inicialização, e o container não fica de pé
Passo 5: construir a imagem, subir na VPS e verificar se o Phoenix respondeu
Pra acessar o servidor, você pode usar SSH ou o terminal web da Hostinger, que já vem no painel com assistência de IA
O fluxo é este:
- construir a imagem a partir do Dockerfile gerado no passo 1
- rodar o container passando as variáveis do passo 3
- executar o
bin/migratecomo no passo 4
O builder e a imagem final usam uma base Debian compatível. A arquitetura também deve corresponder ao destino: a documentação de builds multiplataforma do Docker explica que a conteinerização não elimina a exigência de compatibilidade com a arquitetura do host
Por isso compensa construir a imagem pensando na arquitetura da VPS, em vez de copiar uma pasta _build compilada no seu notebook
Checagem final: o container fica de pé executando /app/bin/server e a aplicação responde na porta definida em PORT
O erro comum deste passo: iniciar a release direto pelo binário padrão, sem PHX_SERVER=true
O processo sobe, mas o endpoint não escuta nada… por isso o CMD do Dockerfile usa bin/server, que já liga o servidor
Se você prefere um painel que cuida do build e dos containers por você, vale olhar como instalar o Coolify na VPS e apontar ele pro mesmo Dockerfile
Erros comuns ao publicar uma release Phoenix com Docker
O container sobe, mas o Phoenix não responde
- Causa: o servidor web não foi ligado, falta
PHX_SERVER=true - Solução: inicie pela
bin/server(oCMDpadrão do Dockerfile gerado) ou defina a variável
Erro de DATABASE_URL ausente na inicialização
- Causa: o
runtime.exsgerado exige a variável em produção - Solução: passe
DATABASE_URLno formatoecto://USER:PASS@HOST/DATABASE
O build quebra na etapa dos assets
- Causa: o projeto usa npm ou yarn e o Dockerfile não baixa esses pacotes
- Solução: adicione o passo de instalação das dependências JS antes do
mix assets.deploy
Comando mix não encontrado em produção
- Causa: o Mix não faz parte da release
- Solução: use
bin/migratee as tarefas dorelease.expras migrações
A release copiada para outro servidor não roda
- Causa: sistema operacional ou arquitetura diferentes da máquina de build
- Solução: construa a release dentro da imagem Docker para o sistema e a arquitetura da VPS, usando bases compatíveis no builder e no estágio final
A aplicação não conecta no banco IPv6
- Causa: o Repo não está configurado pra IPv6
- Solução: no
config/runtime.exs, acrescente[:inet6]aossocket_optionsdo Repo quandoECTO_IPV6fortrueou1(omix phx.gen.releaseavisa isso no terminal quando esse trecho ainda não existe)
Como prevenir
Antes de cada deploy, passa um checklist rápido: SECRET_KEY_BASE, DATABASE_URL, PHX_HOST e PORT definidos, e ECTO_IPV6 se o banco for IPv6
E confirme que o backup semanal automático da VPS está ativo, assim você tem uma camada de segurança caso o deploy dê problema 😀
Próximo passo: manter a release atualizada com segurança na VPS
A cada nova versão da aplicação, o ciclo é sempre o mesmo: gerar a nova imagem, rodar o bin/migrate e só então trocar o container antigo pelo novo
Planeje a ordem conforme a compatibilidade da migration com as versões antiga e nova da aplicação. Como recomendação operacional deste guia, ensaie a alteração em staging e tenha backup antes de modificar o banco; rodar a migration antes da troca, sozinho, não garante que o deploy seja seguro
E se a migração der ruim? A VPS da Hostinger já inclui backups semanais automáticos, com restauração pelo painel, o que ajuda como rede de segurança, mas vale também o hábito de testar a migração num ambiente de staging antes de rodar em produção, sempre que possível
A VPS também tem um prazo de 30 dias pra pedir reembolso (sujeito às condições do programa), o que ajuda se você quer validar o ambiente com calma
Se esse fluxo de release faz sentido pro seu projeto, bora conferir a VPS para este projeto e subir a primeira versão
Até o próximo post! =)
Quer aprender mais sobre inteligência artificial e programação na prática? Conheça o canal Hora de Codar no YouTube e acompanhe os próximos vídeos.
Formações
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 48min
Blog | Mais populares

Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação A popularidade da automação de processos com o n8n está em alta, principalmente […]

Checklist de segurança n8n VPS 2026: proteja sua automação
Confira o checklist de segurança n8n VPS 2026 e mantenha sua automação protegida. Veja práticas essenciais e atualizadas para seu servidor. Manter seu ambiente de […]
Quanto custa uma VPS? Preço atualizado 2026
Quando se fala em quanto custa uma VPS, é preciso contextualizar o tema a partir de um provedor específico, porque o preço só faz sentido […]
