Nix na prática: o que muda (e o que não muda) no dia a dia
Onboarding de 3 dias pra 20 minutos, CI reprodutível, e por que o ciclo edit/build/flash/debug continua exatamente o mesmo.
No post anterior, a gente viu o que o Nix é: derivations como funções puras, store com identidade por hash, flakes com lock file. O modelo mental. A teoria. Agora a pergunta prática: o que muda no dia a dia de um time de firmware que adota Nix?
A resposta curta: menos do que parece. O ciclo de edit, build, flash e debug continua exatamente o mesmo. Os comandos são os mesmos. O editor é o mesmo. O debug probe continua plugado no USB. A diferença é que agora o toolchain é declarado num arquivo, pinado por hash, e reprodutível em qualquer máquina. O dev novo que entrou no time ontem builda com exatamente o mesmo compilador que o dev que saiu há seis meses.
A resposta longa é esse post.
De três dias pra vinte minutos
O cenário clássico de onboarding em firmware: dev novo entra no time, recebe um PDF com 47 passos. Baixa o SDK (versão específica, cuidado pra não pegar a errada). Configura o PATH pro ARM GCC. Seta variáveis de ambiente do CMake. Primeiro build falha. Pede ajuda no chat. Alguém responde duas horas depois. Descobre que baixou o SDK 4.4 em vez do 4.3.2. Reinstala. Segundo build falha. GCC errado. Três dias depois, compila.
Com Nix, o onboarding é cinco comandos e vinte minutos:
curl -sSf -L https://install.determinate.systems/nix | sh -s -- install
git clone [email protected]:empresa/firmware.git
cd firmware
# direnv carrega automaticamente, ou: nix develop
cmake --preset debug && ninja -C build/debug
E quando o segundo dev entra no time? Sem Nix, repete tudo: o mesmo PDF, os mesmos erros, as mesmas horas de espera. Com Nix, o flake.lock já está no repositório. O segundo dev clona, roda nix develop, e obtém exatamente o mesmo toolchain que todo mundo. Doze minutos, primeiro try, toda vez.
O que faz isso funcionar é o flake.nix no root do projeto, que declara exatamente quais ferramentas são necessárias:
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system; };
in {
devShells.default = pkgs.mkShell {
packages = with pkgs; [
gcc-arm-embedded
cmake
ninja
openocd
];
};
});
}
Vinte linhas. gcc-arm-embedded, cmake, ninja, openocd. Quando alguém roda nix develop, o Nix resolve todas as dependências, baixa do binary cache (ou compila se necessário), e coloca tudo no PATH. O flake.lock gerado automaticamente pina cada dependência por hash exato. Dois devs em máquinas diferentes, com o mesmo flake.lock, obtêm exatamente o mesmo toolchain.
Com direnv configurado, nem precisa rodar nix develop manualmente. Um .envrc com use flake no root do projeto carrega o ambiente toda vez que você entra no diretório. O editor detecta as ferramentas via PATH. Tudo automático.
nix-direnv: a huge quality of life improvement
O Determinate Nix Installer é o recomendado pra times. Um comando, funciona em Linux, macOS e WSL2. Habilita flakes por padrão. E se o time decidir que Nix não é pra eles, /nix/nix-installer uninstall remove tudo sem resíduos. Reversível.
Ryan Rasti, que usou Nix por 3 anos num time de 4 engenheiros, resumiu: “New engineers needed only one command: nix develop”. Relatos de times que adotaram Nix consistentemente reportam onboarding caindo de dias pra minutos.
Why Nix Will Win: A 3-Year Production Story
O ciclo não muda
A objeção mais comum quando alguém propõe uma ferramenta nova pro time: “vai mudar tudo”. Com Nix, a resposta é: vai mudar de onde vem o toolchain. O resto continua igual.
O ciclo de desenvolvimento embedded (edit, build, flash, debug) usa os mesmos comandos, os mesmos arquivos, o mesmo editor. A diferença é que o arm-none-eabi-gcc vem do Nix store em vez de uma instalação manual, e a versão é garantida pelo flake.lock em vez de depender de quem configurou a máquina.
Nenhum arquivo existente do projeto é modificado. Nix adiciona dois arquivos ao repositório: flake.nix (~20 linhas) e flake.lock (gerado automaticamente). Opcionalmente, um .envrc de uma linha pra ativação automática com direnv. Quem não usa Nix no time nem percebe que esses arquivos existem.
E o Windows?
Se parte do time usa Windows, a pergunta aparece rápido: “Nix roda no Windows?”. Não nativamente. Roda no WSL2, que pra desenvolvimento embedded funciona surpreendentemente bem.
O setup é: WSL2 com uma distribuição Linux (Ubuntu é o mais simples), Nix instalado dentro do WSL2, e o VSCode rodando nativamente no Windows com a extensão Remote - WSL. O GUI do VSCode fica no Windows, o VSCode Server roda dentro do WSL2, e herda o PATH do Nix automaticamente. clangd, IntelliSense, terminal integrado, tudo funciona como se fosse Linux nativo.
Performance? Segundo a Memfault (referência em embedded), WSL2 é “about 2x the speed of Windows” pra builds de firmware e “similar to bare metal Linux”. O overhead da VM é mínimo pro caso de uso de compilação.
O ponto de atrito é USB. Debug probes (J-Link, ST-Link) plugados no Windows não são visíveis dentro do WSL2 por padrão. A solução é o usbipd-win, um projeto open-source com suporte da Microsoft que faz forwarding de USB via protocolo IP. Três comandos no PowerShell:
usbipd list # lista devices USB
usbipd bind --busid 1-3 # permite sharing
usbipd attach --wsl --busid 1-3 # attach no WSL2
Depois disso, lsusb dentro do WSL2 mostra o probe, e openocd conecta normalmente. O setup inicial leva uns 30 minutos, mas uma vez configurado, o workflow diário é estável. Auto-attach é configurável pra não precisar repetir os comandos toda vez.
Pra quem usa Linux (o resto do time), nada muda. nix develop e pronto. Debug probes plugados no USB funcionam direto, sem nenhuma camada intermediária.
WSL2 for Firmware Development (Interrupt/Memfault)
CI reprodutível
Um CI de firmware com Nix cabe em 4 steps de GitHub Actions:
name: Build Firmware
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- run: nix develop --command bash -c 'cmake --preset release && ninja -C build/release'
O magic-nix-cache-action cuida do caching automaticamente, sem configuração. O toolchain é baixado uma vez e cacheado entre runs. Com cache quente, um CI run de firmware embedded tipicamente leva 1-5 minutos, dependendo do tamanho do projeto (o toolchain em si já está no cache).
A parte que importa: o CI usa o mesmo flake.lock que o dev local. Mesmo nixpkgs, mesmo GCC, mesmas flags. O binário que o CI produz é idêntico ao que o dev produz na máquina dele. Sem surpresas do tipo “compilou aqui mas falhou no CI” por causa de versão diferente de compilador.
Pra manter o toolchain atualizado sem surpresas, o update-flake-lock Action abre PRs automáticos atualizando o flake.lock num schedule configurável (semanal, mensal). O time revisa o diff, testa, e merge quando quiser. Atualização de toolchain vira um PR revisável com diff visível, em vez de “alguém atualizou o GCC na máquina de build sem avisar”.
Como começar sem quebrar nada
A adoção de Nix não precisa ser big bang. O caminho mais seguro é gradual, e cada fase é reversível.
Na primeira semana, um dev (o “Nix champion”) adiciona flake.nix, flake.lock e .envrc ao repositório. Três arquivos, um commit. Ninguém mais no time precisa mudar nada. O build system continua igual, o CI continua igual, quem prefere o setup manual continua usando.
Nas semanas seguintes, devs curiosos instalam Nix e testam nix develop. Comparam o resultado com o setup manual. Sem pressão, sem deadline, sem obrigação. Se funciona melhor, ótimo. Se não, é só não usar.
Se o time gostar, o CI migra pra usar nix develop nos builds. A partir daí, reprodutibilidade é garantida no pipeline. Devs que não usam Nix localmente continuam trabalhando normalmente (o CI que garante o build reprodutível).
E se em qualquer ponto o time decidir que Nix não é pra eles? nix-collect-garbage -d limpa o store. /nix/nix-installer uninstall remove o Nix. Deleta os três arquivos do repo. Zero resíduos. O Nix não modifica nada no sistema fora de /nix/.
Quem já fez isso
Nix pra embedded não é teoria. A Genki Instruments usou Nix pra construir o Katla, um sintetizador polifônico real que é vendido como produto. Na talk de 17 minutos no NixCon 2025, a equipe cobriu como Nix gerencia o dev environment, CI/CD, cross-compilation e otimizações de kernel Linux pra um time distribuído. Os repos públicos da empresa são em Rust, incluindo ferramentas DFU e um fork do Embassy.
Nix as a Solution to Embedded Linux (NixCon 2025)
No ecossistema Rust embedded, Eloy Coto documentou um flake.nix completo pra Cortex-M0 com Embassy, probe-rs, rust-analyzer e até KiCad pro design da PCB. O argumento dele: Nix resolve o problema de “voltar num projeto 6 meses depois e o toolchain não funciona mais”, que em embedded é especialmente comum por causa das dependências de SDK vendor.
Nix Flake for Embedded Programming (Eloy Coto)
Pra quem quer um template pronto, o oddlama/nrf-template é um projeto nRF52840 + Embassy que faz nix build pro firmware binário (não só devShell) e inclui GitHub Actions CI. No mundo ESP32, o nixpkgs-esp-dev com 300 stars é o padrão de facto pra toda a família ESP com Nix.
De volta ao dev novo
Aquele dev que entrou no time e levou três dias pra configurar o ambiente? Com Nix, ele clona o repo, roda um comando, e builda. O toolchain é o mesmo de todo mundo. Sem PDF de 47 passos, sem chat, sem três dias. E se algo mudar no toolchain, a mudança aparece como um PR no flake.lock que o time inteiro revisa antes de merge.
No próximo post, a gente abre o capô e monta um flake.nix completo pra um projeto real: CMake, vendor SDK, flash, debug, CI. Tudo gerenciado por Nix, do zero.