Start Debugging

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.

  1. A imagem do runner não tem SDK nenhum. Imagens de contêiner como ubuntu:24.04, alpine:3.20 ou mcr.microsoft.com/devcontainers/base:ubuntu nã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.
  2. O SDK está instalado, mas não está no PATH para este passo. Cada passo do GitHub Actions roda em um shell novo. Adicionar uma linha em ~/.bashrc de um passo anterior não persiste. Fazer export de PATH dentro de um bloco run: não vaza para o próximo bloco run:.
  3. O SDK está no PATH, mas o global.json fixa uma versão que não está instalada. Quando o dotnet inicia, ele lê o global.json mais próximo subindo a árvore de diretórios e resolve um SDK que case com version e rollForward. Se nada casar, você recebe error NETSDK1045 ou uma falha do host que aparece, dependendo do caso, com cara de “command not found” no script wrapper.
  4. O SDK foi instalado pelo dotnet-install.sh em $HOME/.dotnet, mas DOTNET_ROOT e PATH nunca 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:

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.

Coisas que parecem correções mas não são

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

Fontes

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Voltar