Introdução

Publicar uma biblioteca .NET no NuGet.org não é apenas empacotar um projeto e enviar o .nupkg para o servidor. O ponto mais importante é tratar a API pública como um contrato que outras pessoas e outros times vão consumir. Se eu publicar uma API sem pensar em compatibilidade, o problema não fica só no pacote: ele se espalha para dependências, pipelines e aplicações que dependem do mesmo assembly. Por isso, a publicação segura combina três pilares: contrato estável, versionamento consciente e validação real do artefato em um ambiente de consumidor separado.

A ideia deste artigo é explicar a jornada completa de uma biblioteca .NET: preparar a API, decidir o tipo de distribuição, validar localmente, publicar em um ambiente de teste e, por fim, divulgar a versão estável em nuget.org. Também vou mostrar quando o pacote deve ficar em um feed público e quando ele precisa ir para Azure Artifacts ou GitHub Packages. O objetivo é dar uma visão prática para quem está publicando o primeiro pacote ou tentando organizar a distribuição interna da empresa.

Se você já usou pacotes NuGet em projetos de aplicação, sabe que o fluxo de consumo parece simples na superfície: restaurar, compilar e executar. O ponto delicado é o contrato. A biblioteca que parece compatível no build pode gerar erros de runtime quando o pacote é consumido em um projeto externo com metadados ou dependências diferentes. Por isso, vale separar a publicação em etapas: feed local, ambiente de teste do NuGet, publicação pública e feeds privados. O percurso é simples de entender, mas a disciplina é o que evita retrabalho e quebrar clientes.

⚠️ Atenção: o ambiente de teste do NuGet não substitui um feed privado. Ele serve para validar o fluxo de publicação antes de subir a versão definitiva. Azure Artifacts e GitHub Packages existem para distribuição interna persistente e autenticada.

Pré-requisitos

Antes de empacotar o primeiro pacote, eu gosto de validar alguns preceitos básicos para evitar a sensação de que a publicação foi bem-sucedida e, depois, o pacote não ficou útil para quem vai consumir. Em primeiro lugar, você precisa do .NET SDK instalado e funcional; a checagem mínima é dotnet --info. O lado operacional também importa: uma conta no nuget.org e uma API key com escopo limitado para o pacote. Isso reduz o impacto de vazamento ou uso indevido.

O próximo passo é separar o cenário de distribuição. Se o pacote é público, um fluxo de publicação no nuget.org exige revisão e indexação. Se ele é interno, a melhor alternativa costuma ser Azure Artifacts ou GitHub Packages, com autenticação e controle de acesso. Em qualquer caso, o ideal é nunca expor tokens em arquivos versionados ou em comandos de terminal que fiquem no histórico do shell.

Também vale deixar claro que esse processo não é só de “empacotar e publicar”. É de “empacotar e validar”. A rotina de publicação tem um exercício importante: criar um projeto consumidor separado e instalar o pacote que acabou de ser gerado. Só assim é possível verificar se README, dependências, arquivos e metadados estão corretos.

Defina o Contrato e a Estratégia de Versionamento SemVer

A publicação de bibliotecas .NET exige que o versionamento seja tratado como parte do contrato público. O SemVer é a convenção mais usada no ecossistema e define que a versão siga a estrutura MAJOR.MINOR.PATCH. Cada componente comunica algo diferente:

  • MAJOR indica alteração incompatível de API pública
  • MINOR indica adição de funcionalidade compatível
  • PATCH indica correção de defeito sem mudar a API

Não é só uma convenção estética. O número da versão comunica ao consumidor o risco da atualização. Se eu remover um método público ou mudar o comportamento de forma que um cliente dependa do antigo contrato, a mudança é de MAJOR. Se eu adiciono um novo método a uma API que não quebra ninguém, a mudança é de MINOR. Se eu corrijo um bug internalmente sem alterar a interface, a mudança é de PATCH.

Eu também gosto de reforçar a importância das versões pré-release. Em bibliotecas, 1.5.0-beta.1, 1.5.0-rc.1 e 1.6.0-alpha.3 são úteis para validar uma funcionalidade antes de o pacote ser publicado como estável. Isso reduz o risco de anunciar uma versão definitiva sem testar os impactos reais. A regra mais importante é simples: uma versão publicada é imutável. Se ela estiver errada, você corrige em uma nova versão, não reescreve a antiga.

Mudança no contrato públicoPróxima versãoExemplo
Remover método ou alterar comportamento incompatívelMAJOR1.4.22.0.0
Adicionar método sem quebrar consumidoresMINOR1.4.21.5.0
Corrigir defeito sem alterar contratoPATCH1.4.21.4.3
Validar versão futuraSufixo pré-release1.5.0-beta.1

💡 Dica: o chamado SemVer 2 também permite pré-releases com rótulos e regras de normalização que o NuGet aceita. Isso é importante quando você quer testar uma nova funcionalidade com consumidores que ainda não querem depender dela em produção.

Prepare a Biblioteca para Distribuição

Uma biblioteca pode ser pequena e ainda assim precisa de itens mínimos de qualidade para ser publicada. O projeto de exemplo usado neste artigo está no repositório de exemplos do blog e chama-se Contoso.TextRules. Ele expõe regras reutilizáveis para validação e normalização de texto. A estrutura do pacote é simples, mas a forma como ele é descrito no .csproj é o que permite que o NuGet gere uma página útil no site e que o pacote seja reconhecido corretamente pelo tooling do .NET.

No arquivo de projeto, alguns metadados são fundamentais:

  • PackageId — identifica o pacote
  • Version — define a versão da distribuição
  • Authors — informa autoria
  • Description — resumo útil para busca e descoberta
  • PackageTags — pesquisa e classificação no NuGet.org
  • PackageReadmeFile — inclui README dentro do pacote
  • PackageLicenseExpression — licença SPDX para transparência
  • RepositoryUrl e RepositoryType — ajuda na navegação e rastreabilidade

Além disso, vale empacotar símbolos quando a biblioteca é relevante para depuração e diagnósticos. O dotnet pack também consegue incluir arquivos do pacote, como README.md e LICENSE, o que melhora bastante a experiência de quem vai consumir a biblioteca.

A separação entre abstração e implementação também é relevante como boa prática. O NuGet tem uma cultura muito forte de pacotes pequenos e de responsabilidade clara. Em projetos maiores, é comum separar um contrato da implementação: por exemplo, projetar uma biblioteca de abstração com interfaces e contratos e outra biblioteca com a implementação concreta. Isso evita que consumidores dependam de todo o conjunto de funcionalidades e permite que a API pública evolua com mais clareza. Isso não é obrigatório para todo projeto pequeno, mas é um padrão útil para bibliotecas de plataforma ou componentes de domínio.

📂 Código Fonte: O exemplo completo está disponível no repositório de exemplos do blog: BlogSamples/Packaging/NuGetPublish/

Empacote e Valide Localmente Antes do Push

Depois de preparar a biblioteca, o próximo passo é empacotar o artefato em modo Release. Isso é o ponto em que eu verifico se o projeto realmente gera um pacote completo. O comando principal é:

1
dotnet pack src/BlogSamples/Packaging/NuGetPublish/Contoso.TextRules/Contoso.TextRules.csproj -c Release

O resultado esperado é um .nupkg na pasta bin/Release. Mas o simples fato de existir um arquivo não basta. Eu sempre inspeciono o pacote para confirmar que ele contém o README, a licença e as dependências corretas. O pacote pode compilar e ainda assim ter metadados incompletos ou arquivos faltando, o que costuma aparecer quando a biblioteca é consumida por um projeto externo.

A validação mais importante é consumir o pacote em outro projeto. Isso muda o cenário: o consumidor não está dentro do mesmo repositório nem do mesmo assembly. Ele instala o pacote via dotnet add package ou via feed local, e aí aparecem erros de integração que os testes da própria biblioteca não capturam. Um erro clássico é um arquivo que fica ausente no pacote ou um README que não foi incluído corretamente. O pacote precisa funcionar como um item distribuído, não apenas como um projeto compilado localmente.

Valide no Ambiente de Teste do NuGet

Depois que o pacote está funcionando localmente, o próximo ambiente natural é o teste do NuGet. O endereço oficial é https://int.nugettest.org. Esse ambiente foi criado para validar o fluxo de publicação antes do envio ao feed público. O objetivo não é substituir o processo de produção, e sim confirmar que o pacote pode ser enviado, restaurado e consumido em um cenário semelhante ao real.

Fluxo de publicação de uma biblioteca .NET com pacotes NuGet, ambiente de teste e feeds privados

Em geral, o fluxo é simples: configurar uma fonte de pacote nomeada para o ambiente de teste, autenticar com o usuário e enviar a versão. Com o pacote em mãos, um projeto consumidor diferente pode restaurá-lo e executar o código real. Essa validação reduz o risco de uma falha de packaging ou de uma API pública que ainda não está pronta para o feed público.

ℹ️ Informação: a documentação oficial do NuGet usa int.nugettest.org como ambiente para testar a publicação antes do nuget.org. Pacotes enviados para esse ambiente podem não ser preservados, por isso ele é útil para validação, não para distribuição persistente.

Publique uma Versão Estável no NuGet.org

Quando a API pública está pronta e o pacote passou pela validação de consumo, a próxima etapa é publicar a versão estável em nuget.org. Esse processo exige uma API key do NuGet com escopo mínimo: o ideal é restringir o token ao prefixo do pacote e ao tempo de validade necessário. Isso reduz muito o impacto de vazamento de credenciais.

O comando mais comum é o dotnet nuget push, geralmente com o pacote já empacotado em Release:

1
dotnet nuget push ./artifacts/Contoso.TextRules.1.0.0.nupkg --api-key $NUGET_API_KEY --source https://api.nuget.org/v3/index.json

Na prática, a publicação no nuget.org não é o fim do processo: ela também dispara a indexação e a disponibilidade do pacote para busca e restauração. O pacote passa por validações do NuGet e, em alguns casos, demora alguns minutos para aparecer como disponível para instalação. Isso é normal e faz parte do fluxo oficial. Também vale deixar claro que a decisão de usar unlist faz sentido para remover uma versão da visibilidade pública sem apagá-la por completo; já “excluir” uma versão não é a abordagem recomendada para distribuir um artefato público de forma segura.

A regra mais importante é reservar a primeira estável 1.0.0 para o momento certo. Em bibliotecas, eu prefiro validar a API em pré-release primeiro. Isso dá tempo de ajustar comportamento sem a pressão de “uma versão potencialmente quebrada” já disponível publicamente.

Distribua Bibliotecas Internas com Azure Artifacts e GitHub Packages

Nem todo pacote merece ir para nuget.org. Quando a biblioteca é interna, um feed privado é a opção mais segura e fácil de operar. Azure Artifacts e GitHub Packages atendem a esse cenário com autenticação e controle de acesso. A decisão geralmente depende da assinatura da plataforma usada pela organização.

Azure Artifacts

O Azure Artifacts funciona muito bem quando a organização já usa Azure DevOps e tem pipelines centralizados. O feed de pacotes fica associado à organização e ao projeto do Azure DevOps, e a autenticação pode ser integrada a pipelines e regras de segurança. Para times usando Azure como plataforma principal, essa é uma forma natural de publicar bibliotecas internas com políticas de acesso e revisão de versões.

GitHub Packages

O GitHub Packages se encaixa bem em times que já vivem dentro do GitHub. A autenticação usa token com escopo e permissões mínimas, e a integração com GitHub Actions costuma ser direta. Isso facilita muito a publicação de componentes internos, bibliotecas compartilhas e dependências para repositórios privados e públicos em diferentes fluxos de desenvolvimento.

CritérioAzure ArtifactsGitHub Packages
Plataforma principalAzure DevOpsGitHub
Integração de CIAzure PipelinesGitHub Actions
Controle de acessoOrganização e projetoOrganização e repositório
Melhor cenárioTimes centralizados no Azure DevOpsTimes que já usam GitHub

⚠️ Atenção: nunca deixe tokens em NuGet.Config versionado. O arquivo de configuração pode apontar para diferentes fontes, mas os segredos devem sair do repositório e entrar por variáveis de ambiente ou secret stores do CI.

Automatize Releases sem Quebrar Consumidores

A automação de publicação é importante, mas ela precisa ser um guard rail, não um atalho. O fluxo ideal de CI/CD para uma biblioteca .NET é: validar testes, empacotar, verificar se a versão já existe no feed e só então publicar. Isso evita que uma tentativa de push reescreva uma versão já publicada ou publique um pacote quebrado para o feed errado.

Uma boa estratégia é separar release e publicação em dois momentos. O processo de tag e changelog fica em Git; a publicação de pré-release pode ir para feed privado ou ambiente de teste; a versão estável vai para nuget.org somente após revisão de compatibilidade. Isso ajuda a manter o controle da API pública e faz com que a decisão de publicar uma nova versão seja uma decisão mais consciente.

Em pipelines, a regra é proteger os segredos. O principal é manter o token em um secret do CI, usá-lo em um passo específico de publish e restringir as permissões. O código da biblioteca pode ser automático, mas a decisão de liberar uma nova versão estável continua sendo algo que exige atenção humana e documentação de breaking change.

Exemplo Prático

Vou usar o mesmo cenário do projeto de exemplo: uma biblioteca chamada Contoso.TextRules com regras de validação reutilizáveis. O objetivo é demonstrar a parte do contrato: a API pública fica estável, a versão de pacote inicia como 1.0.0-alpha.1, e a promoção para 1.0.0 acontece depois que a biblioteca é validada em um ambiente de consumidor separado.

Primeiro, a biblioteca em si. O trecho principal mostra como a API pública é simples e previsível:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
namespace BlogSamples.Packaging.NuGetPublish;

public sealed class TextRuleSet
{
    public bool IsValidEmail(string value)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(value);

        return value.Contains('@')
            && value.Contains('.')
            && value.IndexOf('@', StringComparison.Ordinal) > 0;
    }

    public string NormalizeWhitespace(string value)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(value);

        return string.Join(' ', value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
    }
}

📂 Código Fonte: O exemplo completo está disponível no repositório de exemplos do blog: BlogSamples/Packaging/NuGetPublish/Contoso.TextRules/

Em seguida, acrescento os metadados importantes para o pacote:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <PackageId>Contoso.TextRules</PackageId>
    <Version>1.0.0-alpha.1</Version>
    <PackageReadmeFile>README.md</PackageReadmeFile>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <RepositoryUrl>https://github.com/lzocateli/blog-zocateli-sample</RepositoryUrl>
    <RepositoryType>git</RepositoryType>
  </PropertyGroup>
</Project>

A geração do pacote acontece em Release:

1
dotnet pack src/BlogSamples/Packaging/NuGetPublish/Contoso.TextRules/Contoso.TextRules.csproj -c Release

Para consumo em um projeto separado, eu também gosto de configurar uma fonte local ou de teste e instalar o pacote em um consumidor mínimo:

1
2
3
4
dotnet new classlib -n Contoso.TextRules.Consumer
cd Contoso.TextRules.Consumer

dotnet add package Contoso.TextRules --version 1.0.0-alpha.1 --source /caminho/para/o/feed

Esse passo é essencial. Ele confirma que a biblioteca não depende apenas do projeto em que ela foi compilada, mas do artefato que será entregue ao consumidor final. Depois da validação, a promoção para 1.0.0 fica muito mais segura e a decisão de publicar em nuget.org passa a ser uma escolha tecnológica, não só uma sensação de “está funcionando”.

Dicas e Boas Práticas

  1. Trate a API pública como contrato. Qualquer alteração de assinatura, comportamento observável ou semântica documentada precisa ser avaliada pelo impacto em consumidores antes de escolher a versão SemVer.
  2. Consuma o .nupkg em outro projeto. Testar apenas a biblioteca compilada não verifica se o pacote contém README, dependências e assemblies corretos.
  3. Crie API keys e tokens com escopo mínimo. Uma credencial limitada ao prefixo do pacote e à operação de push reduz o impacto de vazamento acidental.
  4. Nunca reutilize uma versão publicada. Corrija o problema em uma nova versão PATCH ou publique uma nova pré-release; isso preserva reprodutibilidade de restore.
  5. Separe fontes públicas e privadas no NuGet.Config. Dê nomes claros às fontes, restrinja mapeamento de pacotes quando apropriado e evite que dependências internas sejam procuradas no nuget.org.
  6. Automatize, mas preserve revisão. O CI deve garantir testes e empacotamento repetíveis; a decisão de uma release estável ainda exige revisão de compatibilidade e changelog.

Resumo Objetivo

  • SemVer — em uma biblioteca .NET, MAJOR representa mudança incompatível, MINOR adiciona funcionalidade compatível e PATCH corrige defeito compatível; pré-releases usam um sufixo como -beta.1.
  • NuGetVersion — o NuGet normaliza versões como 1, 1.0 e 1.0.0 como equivalentes e suporta recursos do SemVer 2 em clientes modernos.
  • Feed local — consumir o .nupkg em um projeto separado verifica o artefato distribuído, incluindo dependências, metadados e arquivos anexados.
  • int.nugettest.org — é o ambiente de teste documentado pelo NuGet para validar publicação antes do nuget.org, mas os pacotes podem não ser preservados.
  • nuget.org — exige uma versão exata no pacote e valida o upload antes de indexá-lo; API keys devem ter escopo e expiração mínimos.
  • Feeds privados — Azure Artifacts e GitHub Packages distribuem bibliotecas internas com autenticação e controle de acesso, função diferente da homologação no ambiente de teste público.

Leia Também

Referências