Bun e Ubuntu: quando o sistema muda embaixo do seu script
Você atualiza o Ubuntu numa sexta à tarde e na segunda o script de deploy que funcionava há dois anos para de funcionar. Nenhuma linha do seu código mudou. Quem mudou foi o chão. Esse é o espírito do post WTF Ubuntu?, do Chalmovsky, um desabafo de quem cansou de ser surpreendido pela própria distro. Eu li e pensei logo no meu dia a dia com Bun e Ubuntu. Dá para tirar uma lição prática disso, e ela tem pouco a ver com trocar de distro.
O que anda incomodando quem usa Ubuntu
O incômodo não vem de um bug isolado. Vem do padrão: a Canonical toma decisões grandes e a gente só descobre quando algo quebra.
Alguns exemplos que todo dev que usa Ubuntu já viu:
- Snap por padrão. O Firefox virou snap no 22.04. Inicialização mais lenta, caminhos de arquivo diferentes, permissões confinadas. Quem tinha automação de navegador sentiu.
- Coreutils em Rust. No 25.10 o Ubuntu passou a usar o uutils, uma reescrita em Rust de
ls,cp,datee companhia. A ideia é boa. Só que logo no começo umdate -rcom comportamento diferente quebrou a checagem automática de atualizações do próprio sistema. - sudo-rs. Também no 25.10, o
sudoclássico deu lugar a uma reimplementação em Rust.
Não sou contra nenhuma dessas mudanças em si. Segurança de memória é um ganho real. O problema é outro: coreutils é contrato. Milhões de scripts dependem de flags e mensagens de erro que ninguém documentou direito, mas que todo mundo usa.
Seu script de build depende de muito mais coisa do que o seu package.json mostra.
Por que isso importa para quem usa Bun no Ubuntu
Pense no seu package.json. Quantos scripts ali chamam o sistema?
{
"scripts": {
"clean": "rm -rf dist && mkdir -p dist",
"build": "bun build ./src/index.ts --outdir dist",
"stamp": "date -u +%Y%m%d%H%M > dist/BUILD",
"release": "cp -r public/* dist/ && sudo systemctl restart app"
}
}
Quatro scripts, e pelo menos cinco binários do sistema operacional: rm, mkdir, date, cp, sudo. O Bun cuida do TypeScript, mas o resto é responsabilidade da distro. Se o date muda de comportamento, seu stamp quebra. Se o cp trata glob ou link simbólico de outro jeito, seu release sai incompleto.
O pior é quando não dá erro nenhum: o comando roda, retorna 0 e entrega um resultado levemente diferente. Você só percebe em produção.
Quem roda tudo no macOS e faz deploy em Ubuntu já conhece o drama: sed -i no BSD e no GNU têm sintaxes diferentes. Agora o drama pode acontecer entre duas versões do mesmo Ubuntu.
Bun Shell: o seu script deixa de depender da distro
Aqui o Bun tem uma resposta que pouca gente usa. O Bun.$, o Bun Shell, é um shell embutido no runtime. E o ponto principal: comandos comuns como ls, rm, mkdir, cp, mv, cat, echo, which e basename são implementados pelo próprio Bun. Ele não chama o binário do sistema para esses casos.
Então aquele script frágil vira um arquivo TypeScript:
// scripts/release.ts
import { $ } from "bun";
await $`rm -rf dist`;
await $`mkdir -p dist`;
await $`bun build ./src/index.ts --outdir dist`;
await $`cp -r public dist`;
// nada de depender do `date` da distro
await Bun.write("dist/BUILD", new Date().toISOString());
E no package.json:
{
"scripts": {
"release": "bun scripts/release.ts"
}
}
O que você ganha na prática:
- O mesmo comportamento no Ubuntu 22.04, no 25.10, no macOS e até no Windows.
- Interpolação segura.
$\rm ${caminho}`` escapa a variável e evita aquele bug clássico de espaço no nome da pasta. - Erro vira exceção. Se um comando falha, o
awaitlança erro e o script para. Nada de esquecer oset -e.
Tem limite, claro. sudo e systemctl continuam sendo do sistema e o Bun Shell vai chamar o binário real. Se o comando não é builtin, ele procura no PATH como qualquer shell faria. O ganho está em tirar do caminho o que é trivial, que costuma ser 80% do script.
Fixe as versões, inclusive a do Bun
Reclamar que o Ubuntu mudou embaixo de você e deixar o Bun "latest" no CI é incoerente. O raciocínio vale para tudo: o que muda sem você pedir vai quebrar num dia ruim.
No GitHub Actions:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.1
No Docker, fuja da tag latest:
FROM oven/bun:1.3.1-slim
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
CMD ["bun", "src/index.ts"]
O --frozen-lockfile faz o install falhar se o lockfile não bate com o package.json. Parece chato. É exatamente o comportamento que você quer no servidor.
E aqui vai minha opinião forte: em produção, a versão do Ubuntu do host não deveria importar para a sua aplicação. Se importa, você está acoplado demais à máquina. O host roda o Docker e o kernel. O resto vem da imagem, com versão fixa, construída do mesmo jeito em qualquer lugar.
E na máquina de desenvolvimento?
Na sua máquina não dá para colocar tudo em container sem perder agilidade. Algumas coisas simples ajudam:
- Instale o Bun pelo script oficial ou pelo
mise, não por snap. O snap confina o binário e pode dar dor de cabeça com acesso a arquivos fora da home e com links simbólicos donode_modules. - Não atualize a distro no meio de uma entrega. Parece óbvio. Mesmo assim, todo mundo já fez
do-release-upgradenuma quinta à noite. - Prefira LTS para trabalhar. As versões intermediárias, como a 25.10, servem justamente para testar mudanças como o uutils. Se você usa uma delas no notebook de trabalho, você é o beta tester, e tudo bem, desde que saiba disso.
- Teste o script de build num container limpo antes de culpar o código. Um
docker run --rm -v $PWD:/app -w /app oven/bun:1.3.1 bun run releaseresponde em segundos se o problema é seu ou do ambiente.
Uma confissão: eu já perdi uma manhã inteira caçando um "bug" no build que era um cp com alias no meu .zshrc. O sistema não tinha mudado nada. Era eu. Isolar o ambiente também protege você de você mesmo.
Vale trocar de distro?
Talvez. Fedora, Debian e Pop!_OS têm fãs com bons argumentos. Mas trocar de distro só troca as surpresas de lugar. O Debian também vai mudar algo um dia. O Fedora muda até mais rápido.
O problema de verdade é deixar o build depender de coisas que você não controla. O Ubuntu só deixou isso visível. Se o seu package.json está cheio de rm, cp e date, essa é a hora de passar esses scripts para Bun.$ e fixar versão em tudo que roda no CI. Leva uma tarde e você deixa de ter medo do próximo upgrade.
O Ubuntu vai continuar tomando decisões que irritam. Só não precisa ser a sua distro que decide se o deploy de segunda funciona. Se quiser ver como eu organizo isso nos meus projetos com Bun, dá uma olhada em o que eu ando construindo.
Resumo para o LinkedIn
Atualizei o Ubuntu e o deploy quebrou sem eu mexer em uma linha de código. O culpado estava no chão: snap, coreutils reescrito em Rust, sudo-rs. Coreutils é contrato, e milhões de scripts dependem de flags que ninguém documentou. Olhei meu package.json e vi rm, cp, date e sudo, todos binários da distro. O pior erro é o que roda, retorna 0 e entrega outra coisa. Minha saída foi passar o trivial para o Bun Shell, que implementa ls, rm, cp e mkdir no próprio runtime. Agora o comportamento é o mesmo no Ubuntu, no macOS e no Windows. E fixar versão em tudo: Bun no CI, imagem Docker sem latest, --frozen-lockfile. Em produção, a versão do Ubuntu do host não deveria importar para a sua aplicação. Trocar de distro só troca a surpresa de lugar. Leva uma tarde para parar de ter medo do próximo upgrade. Se quiser ver como organizo isso nos meus projetos, o link está nos comentários. #Bun #Ubuntu #DevOps #JavaScript #Linux