Voltar para o blog
BunUbuntuLinuxDevOps

Bun e Ubuntu: quando o sistema muda embaixo do seu script

03 de novembro de 2026·6 min de leitura·Diego Horvatti

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, date e companhia. A ideia é boa. Só que logo no começo um date -r com comportamento diferente quebrou a checagem automática de atualizações do próprio sistema.
  • sudo-rs. Também no 25.10, o sudo clá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 await lança erro e o script para. Nada de esquecer o set -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:

  1. 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 do node_modules.
  2. Não atualize a distro no meio de uma entrega. Parece óbvio. Mesmo assim, todo mundo já fez do-release-upgrade numa quinta à noite.
  3. 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.
  4. 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 release responde 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