sh4.tech

Lab parte 1

Construindo este site em Elixir, parte 1

4 min de leitura

Por que Elixir, por que Docker e como os posts acabam compilados dentro do app. As primeiras anotações da construção do sh4.tech.

Tema
Elixir · Phoenix
Stack
Elixir 1.18, OTP 28, Phoenix 1.8, NimblePublisher, Docker
Também em
English
  • 1.18Elixir
  • 1.8Phoenix
  • 0bancos de dados
  • 2idiomas

Por que Elixir

Eu montaria este site numa tarde com as ferramentas que uso todo dia. Escolhi Elixir mesmo assim, por três motivos. Primeiro, quero aprender, e aprendo melhor quando tem algo de verdade para entregar no fim. Segundo, a BEAM. Por dez anos cuidei de sistemas em que milhares de dispositivos falam com os servidores ao mesmo tempo, e o Elixir foi feito para esse tipo de carga: muitos processos pequenos, cada um barato, supervisionado, reiniciado quando cai. Um blog não precisa de nada disso. Mas é um bom lugar para aprender o modelo antes de precisar dele num trabalho de verdade.

Terceiro, o próprio site é um case. Quando estiver no ar, a página que você está lendo sai de um app Phoenix que eu escrevi enquanto aprendia a linguagem.

Docker, porque o apt é velho

O primeiro problema apareceu antes de qualquer código. apt install elixir no Ubuntu traz o Elixir 1.14, e a versão do Phoenix que eu queria pede uma mais nova. Não quis brigar com gerenciador de versão na minha máquina, então tudo roda no Docker: a imagem oficial elixir:1.18 com OTP 28, a pasta do projeto montada como volume, porta 4000 aberta. docker compose up e o site está lá. Nada instalado na máquina, fora o próprio Docker.

Todo comando do mix passa pelo container:

bash
docker compose exec -T web mix test
docker compose exec -T web mix compile --warnings-as-errors

Phoenix 1.8, sem Ecto

Gerei o projeto sem Ecto, então não tem banco de dados. Os posts são arquivos Markdown no repositório, uma pasta por idioma: priv/posts/en/ e priv/posts/pt/. Sem login, sem comentários, sem painel de admin. Quando quero mudar um post, edito o arquivo e faço commit, igual a qualquer outra mudança de código.

Os posts são compilados dentro do app

É a parte de que eu mais gosto. O NimblePublisher lê todos os arquivos Markdown na compilação, converte para HTML e guarda a lista dentro do módulo compilado. Em produção não tem leitura de disco nem consulta para servir uma página. É uma função devolvendo dados que já estão na memória.

Para cada arquivo ele chama uma função build/3 que eu escrevo. O caminho do arquivo traz o idioma e a data, e o frontmatter fica curto:

text
priv/posts/pt/2026-10-22-codecs-clickhouse-por-coluna.md
           ^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         idioma  data                 slug
sheet 1 · rev. 2026-10 en + pt *.md priv/posts/ NimblePublisher Earmark + makeup Sh4.Blog @posts sem banco de dados os posts ficam na memória Phoenix Docker · :4000 nginx HTTPS próximo na compilação · mix compile roda uma vez, antes do deploy a cada visita lê da memória
Como este site é montado e servido hoje. role →

A caixa tracejada da direita ainda não existe. Falo dela no fim.

Dois idiomas na URL

Toda página existe em /en/... e /pt/.... Quem valida o idioma é um plug: uma função pequena que recebe a conexão e devolve a conexão, talvez modificada. Em Elixir isso são duas cláusulas da mesma função, e o runtime escolhe a que casa.

lib/sh4_web/plugs/locale.ex
def call(%Plug.Conn{path_params: %{"locale" => locale}} = conn, _opts)
    when locale in @locales do
  Gettext.put_locale(Sh4Web.Gettext, locale)

  conn
  |> assign(:locale, locale)
  |> assign(:alternates, Map.new(@locales, &{&1, swap_locale(conn.request_path, &1)}))
end

def call(conn, _opts) do
  raise Phoenix.Router.NoRouteError, conn: conn, router: Sh4Web.Router
end

Se o primeiro pedaço do caminho é en ou pt, roda a primeira cláusula. Qualquer outra coisa cai na segunda e vira 404, igual a uma rota que não existe. Rótulos curtos como "Artigos" passam pelo Gettext. Os textos longos da home são um mapa por idioma num módulo só, porque é mais fácil revisar um parágrafo inteiro em português ao lado do inglês do que caçar pedaços num arquivo .po.

Design: três propostas às cegas, depois a de verdade

Pedi três propostas de design sem o briefing completo: uma de paddock, um caderno de campo e um estúdio. Às cegas de propósito, para ver o que sai quando ninguém ainda sabe o que eu quero. Depois veio uma quarta versão com o posicionamento real, e é essa que está no app hoje.

Duas coisas da rodada às cegas voltaram depois. A régua logarítmica da home vem da proposta do estúdio. E este Lab, com o papel quadriculado e as notas na margem, vem do caderno.

Próximos passos

  • Busca e filtro por tag com LiveView, sem recarregar a página. É aí que processos e estado começam a importar.
  • Deploy: uma release do Elixir num container no meu próprio servidor, atrás do nginx que já roda lá, com Let's Encrypt. Primeiro num subdomínio de teste, depois o DNS.

A parte 2 vai ser sobre LiveView, provavelmente começando pela caixa de busca.

Luciano Serra
Luciano Serra Founder-engineer · cofundador e CTO, construiu do zero uma rede de 15.000 telas, 2015–2024 · São Paulo, remoto

Desenho sistemas de produção do zero e assumo os que já estão rodando. Nos dois casos eles precisam aguentar quando o negócio cresce. Cofundei uma empresa de mídia out-of-home e construí a tecnologia dela por dez anos; meus sócios captaram o dinheiro e cuidaram do comercial.

Seu negócio está crescendo mais rápido que os sistemas? Vamos conversar →