Correção: The command 'dotnet' could not be found no CI
Seu runner de CI não consegue resolver dotnet porque o SDK não está instalado para esse passo, ou está instalado mas fora do PATH. Use actions/setup-dotnet, fixe um global.json e exporte DOTNET_ROOT e ~/.dotnet/tools.
A correção: um passo de CI está rodando dotnet em um shell onde o SDK ou não está instalado, ou não está no PATH, ou está fixado em uma versão que o seu global.json proíbe. No GitHub Actions, adicione um passo actions/setup-dotnet@v4 antes de qualquer invocação de dotnet, comite um global.json que case com o SDK que você pediu e, em contêineres Linux, exporte DOTNET_ROOT e $HOME/.dotnet/tools. O erro quase nunca é bug da imagem do runner.
/bin/bash: line 1: dotnet: command not found
##[error]Process completed with exit code 127.
ou em runners Windows:
dotnet : The term 'dotnet' is not recognized as the name of a cmdlet, function, script file, or operable program.
At line:1 char:1
+ dotnet build
+ ~~~~~~
+ CategoryInfo : ObjectNotFound: (dotnet:String) [], CommandNotFoundException
ou, no Ubuntu após dotnet-install.sh:
Command 'dotnet' not found, but can be installed with:
sudo apt install dotnet-host
Este guia foi escrito contra .NET 11 (SDK 11.0.100), actions/setup-dotnet@v4.0.1, a task UseDotNet@2 do Azure DevOps versão 2.213.x e o dotnet-install.sh publicado em https://dot.net/v1/dotnet-install.sh em maio de 2026. As causas raiz não mudaram desde o .NET Core 3.1; só mudaram as versões das actions.
Por que shells de CI perdem o dotnet
São quatro causas raiz. Elas se confundem fácil porque todas mostram a mesma linha command not found, então vale saber qual delas você está olhando antes de remendar o YAML.
- A imagem do runner não tem SDK nenhum. Imagens de contêiner como
ubuntu:24.04,alpine:3.20oumcr.microsoft.com/devcontainers/base:ubuntunão trazem o SDK do .NET. Os runners hospedados pelo GitHub (ubuntu-latest,windows-latest) trazem, mas a versão em cache é a que o runner cozinhou na imagem, não a que o seu repositório precisa. - O SDK está instalado, mas não está no
PATHpara este passo. Cada passo do GitHub Actions roda em um shell novo. Adicionar uma linha em~/.bashrcde um passo anterior não persiste. FazerexportdePATHdentro de um blocorun:não vaza para o próximo blocorun:. - O SDK está no
PATH, mas oglobal.jsonfixa uma versão que não está instalada. Quando odotnetinicia, ele lê oglobal.jsonmais próximo subindo a árvore de diretórios e resolve um SDK que case comversionerollForward. Se nada casar, você recebeerror NETSDK1045ou uma falha do host que aparece, dependendo do caso, com cara de “command not found” no script wrapper. - O SDK foi instalado pelo
dotnet-install.shem$HOME/.dotnet, masDOTNET_ROOTePATHnunca foram definidos. Essa é a falha mais comum em runners auto-hospedados Linux e dentro de contêineres Docker. O script instala direitinho, e nenhum passo seguinte exporta as variáveis.
Uma reprodução mínima no CI
Salve isto como .github/workflows/build.yml e dê push em um repositório com um .csproj:
# .github/workflows/build.yml -- .NET 11, GitHub Actions May 2026
name: build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
container: ubuntu:24.04 # no SDK is preinstalled here
steps:
- uses: actions/checkout@v4
- run: dotnet --info # fails: dotnet: command not found
A chave container: troca o SO do runner por uma imagem Ubuntu crua. O runner padrão ubuntu-latest tem o SDK, então remover container: faz esse snippet funcionar. A maioria dos times bate nisso ao mover um job para um contêiner por reprodutibilidade e esquecer de levar o setup-dotnet junto.
Correção 1: instale o SDK no mesmo job, depois use
A correção canônica no GitHub Actions é o actions/setup-dotnet. Coloque antes de qualquer passo que chame dotnet. Ele baixa o SDK para um cache por runner, prefixa o PATH em todos os passos seguintes e exporta DOTNET_ROOT para ferramentas que precisam do diretório de instalação do SDK diretamente.
# .github/workflows/build.yml -- setup-dotnet@v4
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "11.0.x"
- run: dotnet --info
- run: dotnet build -c Release
Dois detalhes que mordem:
dotnet-versionaceita curinga, mas mesmo assim comite umglobal.jsonpara que build local e CI batam. Sem ele, um desenvolvedor com SDK 11.0.5 instalado local e CI no 11.0.7 podem produzirobj/project.assets.jsondiferentes e se surpreender.global-json-file:sobrescrevedotnet-versionnosetup-dotnet@v4. Se você passar os dois, o JSON vence. Isso é recurso, não bug, mas já vi gente adicionardotnet-version: "8.0.x"a um workflow comglobal.jsonapontando para 11 e ficar se perguntando por que ainda instala .NET 11.
No Azure DevOps, o equivalente é UseDotNet@2:
# azure-pipelines.yml -- Azure DevOps, UseDotNet@2
steps:
- task: UseDotNet@2
inputs:
packageType: sdk
version: "11.0.x"
- script: dotnet build -c Release
No GitLab CI ou Buildkite, o caminho mais limpo é uma imagem base com o SDK já cozido (mcr.microsoft.com/dotnet/sdk:11.0). Evite rodar dotnet-install.sh no próprio job a menos que precise: funciona, mas todo job paga o custo de download.
Correção 2: comite um global.json que case com o CI
Quando o CI roda dotnet build, ele usa o SDK que vence a resolução do global.json, não o último SDK instalado. Uma falha comum tem essa cara:
A compatible .NET SDK was not found.
Requested SDK version: 11.0.200
global.json file: /home/runner/work/myrepo/myrepo/global.json
Installed SDKs:
8.0.412 [/usr/share/dotnet/sdk]
11.0.100 [/usr/share/dotnet/sdk]
O runner tem 11.0.100; o global.json pede 11.0.200. O script wrapper sai com código diferente de zero, e dependendo do host, você vê “command not found” propagado de um if em Bash que engoliu o erro real.
Mantenha o global.json honesto:
{
"sdk": {
"version": "11.0.100",
"rollForward": "latestFeature"
}
}
rollForward: latestFeature deixa um desenvolvedor com 11.0.103 trabalhar sem ter que subir o arquivo a cada release de patch. latestMajor é permissivo demais para CI; disable é rígido demais para local. Faça version casar com o que o dotnet-version do actions/setup-dotnet vai instalar.
Correção 3: quando você precisa usar dotnet-install.sh
Dentro de um contêiner enxuto, ou num runner auto-hospedado onde você não pode usar setup-dotnet, instale com o script oficial e exporte as variáveis explicitamente em cada passo seguinte.
# self-hosted runner or restrictive container -- .NET 11
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Install .NET 11 SDK
run: |
curl -sSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh
chmod +x dotnet-install.sh
./dotnet-install.sh --channel 11.0 --install-dir "$HOME/.dotnet"
echo "$HOME/.dotnet" >> "$GITHUB_PATH"
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
echo "DOTNET_ROOT=$HOME/.dotnet" >> "$GITHUB_ENV"
- run: dotnet --info
- run: dotnet tool restore && dotnet build -c Release
As duas linhas echo escrevem em arquivos especiais que o GitHub Actions lê entre passos: GITHUB_PATH prefixa uma entrada no PATH em todos os passos seguintes do job, e GITHUB_ENV exporta uma variável de ambiente da mesma forma. export PATH=... dentro do mesmo bloco run: não funcionaria para o próximo passo, que é a armadilha em que as pessoas caem ao traduzir um script de shell literalmente.
DOTNET_ROOT importa mesmo que o PATH esteja configurado. O host (o binário dotnet) usa DOTNET_ROOT para encontrar as pastas shared/Microsoft.NETCore.App e sdk/. Se você só corrigir o PATH, pode acabar com dotnet --info funcionando, mas dotnet build falhando com erro de host sobre um runtime faltando. Segundo a Microsoft Learn, DOTNET_ROOT é lido pelo host no Linux e macOS, e no Windows quando a instalação está fora do local padrão.
Adicione também o diretório tools. Sem $HOME/.dotnet/tools no PATH, qualquer chamada dotnet tool install --global tem sucesso mas a ferramenta fica inacessível, produzindo o erro relacionado: dotnet-ef: command not found.
Correção 4: imagem do SDK pré-construída, sem passo de instalação
Para CI baseado em Docker, o caminho de menor atrito é começar de uma imagem que já tem o SDK:
# .gitlab-ci.yml -- pinned SDK image, no install step
build:
image: mcr.microsoft.com/dotnet/sdk:11.0
script:
- dotnet --info
- dotnet build -c Release
Replique isto no Buildkite, CircleCI, agentes Jenkins em Docker e qualquer plataforma cuja primitiva de CI seja “um contêiner mais um script”. Você troca flexibilidade (uma imagem, um SDK) pela garantia de que dotnet está no PATH desde o primeiro comando.
Variantes comuns e parecidos
Buscas que caem nesta página às vezes querem um erro um pouco diferente. Vale desambiguar logo no começo para não perseguir a correção errada.
dotnet-ef: command not found. A ferramenta global foi instalada mas$HOME/.dotnet/toolsnão está noPATH. Adicione como mostrado acima, ou use um manifesto localdotnet-tools.jsone chamedotnet tool restore && dotnet ef.Could not execute because the specified command or file was not found.dotnetestá noPATH, mas o subcomando (dotnet foo) não é built-in nem está instalado como ferramenta. Outro erro, outra causa raiz.error NETSDK1045: The current .NET SDK does not support targeting .NET 11.0. O SDK está noPATH, mas é velho demais para oTargetFrameworkdo projeto. Suba odotnet-versiondosetup-dotnet(ou oglobal.json), não instale um segundo SDK ao lado do primeiro esperando que a resolução multi-target resolva./usr/bin/env: 'dotnet': No such file or directory. Mesma causa raiz de “command not found”, outro shell. A correção é idêntica.A fatal error occurred. The required library libhostfxr.so could not be found.dotnetestá noPATH, mas oDOTNET_ROOTaponta para um diretório vazio, ou o SDK foi instalado parcialmente. Rodedotnet-install.shde novo e confirme queDOTNET_ROOTcasa com o diretório de instalação real.
Coisas que parecem correções mas não são
- Rodar
apt install dotnet-hostno CI. Isso instala só o host, não o SDK, e puxa um.debassinado pela Microsoft que pode estar semanas atrás do canal do SDK. Usesetup-dotnetoudotnet-install.sh. - Adicionar
dotnetaoPATHno~/.bashrcdentro de um passorun:. Passos de CI usam shells não interativos;~/.bashrcnão é carregado. UseGITHUB_PATH(GitHub Actions),task.prependpath(Azure DevOps), ou um prefixoPATH=...por passo. sudonum runner hospedado. Runners hospedados já rodam como usuário comsudosem senha, mas o SDK é instalado em/usr/share/dotnete o wrapper em/usr/bin/dotnetjá está lá. Se você se pegar dandosudopara fazer funcionar, quase com certeza está faltandosetup-dotnet, não privilégio.- Fixar
actions/setup-dotnetnum major antigo porque “v4 nos quebrou”. v4 mudou diretórios de cache e passou a parsearglobal.jsoncom mais rigor. A quebra é quase sempre umglobal.jsonapontando para um SDK indisponível. Conserte o JSON; não fique fixado em v3 para sempre.
Verificando a correção no CI
Antes de seguir, rode dois passos de diagnóstico. Eles são baratos e poupam você de caçar fantasmas na saída de dotnet build.
- run: which dotnet || command -v dotnet || true
- run: dotnet --info
which dotnet (ou where dotnet no Windows) confirma qual binário o shell resolve. dotnet --info imprime o runtime, a lista de SDKs e o global.json resolvido. Se --info tem sucesso mas build falha com “command not found”, a falha está dentro de um wrapper que engole erros, não no dotnet. Esse é o momento de ler o wrapper, não de reinstalar.
Quando a saída de --info mostrar o SDK que você pediu, apontar Base Path: para o diretório esperado e listar global.json file: <seu caminho>, você terminou. Qualquer outra coisa é configuração errada de verdade que vale corrigir.
Relacionados
- Para o panorama maior de rodar ferramentas em lanes paralelas de CI, veja como mirar várias versões do Flutter em um único pipeline de CI, que usa o mesmo truque do
GITHUB_PATHpara trocar SDKs por job de matriz. - Se o build falhar depois do SDK ser encontrado, olhe por que um app publicado falha ao carregar assemblies para a história de trim e runtime packs.
- Para falhas específicas de cópia em build, a correção do MSB3027 com retry-count cobre antivírus e bloqueio de arquivo.
- Para uma ferramenta de EF Core que resolve mas falha ao se anexar ao host, veja corrigir dotnet ef migrations add quando o DbContext não pode ser criado.
- Para testes de integração baseados em contêiner quando você quer um banco real no mesmo job, testes de integração contra um SQL Server real com Testcontainers percorre um pipeline funcional.
Fontes
- README do
actions/setup-dotnet, documentaçãov4.0.xdedotnet-version,global-json-fileecache. - Instalar .NET no Linux sem gerenciador de pacotes, Microsoft Learn, cobre
dotnet-install.sh,DOTNET_ROOTePATH. - Variáveis de ambiente usadas pelo SDK e CLI do .NET, Microsoft Learn, sobre
DOTNET_ROOT. - Visão geral do
global.json, Microsoft Learn, para as regras derollForward. - Comandos de workflow para o GitHub Actions, GitHub Docs, sobre
GITHUB_PATHeGITHUB_ENV. - Issue 5267 de
dotnet/core, a thread upstream de longa data sobre “command ‘dotnet’ not found, but can be installed with”.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.