How to Publish a .NET Application from a Batch Script
Publishing a .NET application creates a self-contained, deployable package that includes the compiled application, its dependencies, configuration files, and optionally the .NET runtime itself. Unlike a simple build, publishing produces output that is ready to be copied directly to a server and run without requiring the .NET SDK on the target machine. Automating this process through Batch Script ensures consistent, reproducible deployments.
In this guide, we will explore how to publish .NET applications from a Batch Script using the dotnet publish command, covering framework-dependent and self-contained deployments, runtime identifiers, single-file publishing, and production-grade workflows.
Understanding Publish Modes
| Mode | Flag | Runtime Required on Server? | Output Size |
|---|---|---|---|
| Framework-dependent | (default) | Yes (.NET Runtime) | Small |
| Self-contained | --self-contained | No | Large (includes runtime) |
| Single-file | /p:PublishSingleFile=true | Depends on mode | Single executable |
| ReadyToRun (R2R) | /p:PublishReadyToRun=true | Depends on mode | Larger, faster startup |
Method 1: Basic Framework-Dependent Publish
@echo off
setlocal
set "project=MyWebApp"
set "config=Release"
set "output=publish"
:: Verify dotnet CLI is available
where dotnet >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] dotnet CLI not found. Install the .NET SDK.
pause
exit /b 1
)
echo Publishing %project% (%config%^)...
:: Clean output
if exist "%output%" rmdir /s /q "%output%"
dotnet publish "%project%" -c %config% -o "%output%"
if %errorlevel%==0 (
echo.
echo [SUCCESS] Published to: %output%
echo.
echo Contents:
dir /b "%output%\*.dll" "%output%\*.exe" "%output%\*.json" 2>nul
) else (
echo.
echo [ERROR] Publish failed.
exit /b 1
)
pause
Method 2: Self-Contained Publish
Include the .NET runtime so the target server does not need .NET installed:
@echo off
setlocal
set "project=MyWebApp"
set "config=Release"
set "rid=win-x64"
set "output=publish\%rid%"
echo Publishing self-contained for %rid%...
if exist "%output%" rmdir /s /q "%output%"
dotnet publish "%project%" -c %config% -r %rid% --self-contained -o "%output%"
if %errorlevel%==0 (
echo.
echo [SUCCESS] Self-contained publish complete.
echo Output: %output%
echo.
echo The application can run on any Windows x64 machine without .NET installed.
) else (
echo [ERROR] Publish failed.
exit /b 1
)
pause
Common Runtime Identifiers (RIDs)
| RID | Platform |
|---|---|
win-x64 | Windows 64-bit |
win-x86 | Windows 32-bit |
win-arm64 | Windows ARM64 |
linux-x64 | Linux 64-bit |
linux-arm64 | Linux ARM64 |
osx-x64 | macOS Intel |
osx-arm64 | macOS Apple Silicon |
Method 3: Single-File Publish
Create a single executable that bundles everything:
@echo off
setlocal enabledelayedexpansion
set "project=MyConsoleApp"
set "config=Release"
set "rid=win-x64"
set "output=publish\single"
echo Publishing as single file...
if exist "%output%" rmdir /s /q "%output%"
dotnet publish "%project%" -c %config% -r %rid% --self-contained ^
/p:PublishSingleFile=true ^
/p:PublishTrimmed=true ^
/p:IncludeNativeLibrariesForSelfExtract=true ^
-o "%output%"
if !errorlevel!==0 (
echo.
echo [SUCCESS] Single file published.
for %%F in ("%output%\*.exe") do (
echo File: %%~nxF
echo Size: %%~zF bytes
)
) else (
echo [ERROR] Failed.
exit /b 1
)
pause
/p:PublishTrimmed=true removes unused framework code, reducing the single-file size significantly. However, trimming can break applications that use reflection. Test thoroughly after enabling trimming.
Method 4: Full Publish Pipeline with Versioning
@echo off
setlocal enabledelayedexpansion
:: Configuration
set "project=MyWebApp"
set "config=Release"
set "publish_dir=artifacts\publish"
set "package_dir=artifacts\packages"
:: Verify dotnet CLI
where dotnet >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] dotnet CLI not found.
exit /b 1
)
echo =============================================
echo .NET PUBLISH PIPELINE
echo =============================================
echo.
:: Version
set "version=1.0.0"
if exist VERSION (
set /p "version=" < VERSION
)
set "build=0"
if exist BUILD_NUMBER (
set /p "build=" < BUILD_NUMBER
)
set /a build+=1
>BUILD_NUMBER echo !build!
set "full_version=!version!.!build!"
echo Version: !full_version!
echo.
:: Stage 1: Clean
echo [1/4] Cleaning...
if exist "%publish_dir%" rmdir /s /q "%publish_dir%"
if exist "%package_dir%" rmdir /s /q "%package_dir%"
mkdir "%publish_dir%"
mkdir "%package_dir%"
dotnet clean "%project%" -c %config% --verbosity quiet >nul 2>&1
echo Done.
:: Stage 2: Test
echo [2/4] Running tests...
if exist "%project%.Tests" (
dotnet test "%project%.Tests" -c %config% --verbosity quiet
if !errorlevel! neq 0 (
echo [FAIL] Tests failed. Aborting publish.
exit /b 1
)
echo Done.
) else (
echo No test project found (%project%.Tests^). Skipping.
)
:: Stage 3: Publish
echo [3/4] Publishing...
dotnet publish "%project%" -c %config% -o "%publish_dir%" ^
/p:Version=!full_version! ^
/p:AssemblyVersion=!full_version! ^
/p:InformationalVersion=!full_version!
if !errorlevel! neq 0 (
echo [FAIL] Publish failed.
exit /b 1
)
echo Done.
:: Add build info
for /f "tokens=2 delims==" %%T in ('wmic os get LocalDateTime /value') do set "dt=%%T"
set "build_date=%dt:~0,4%-%dt:~4,2%-%dt:~6,2% %dt:~8,2%:%dt:~10,2%:%dt:~12,2%"
(
echo version=!full_version!
echo published=!build_date!
echo machine=%COMPUTERNAME%
echo config=%config%
) > "%publish_dir%\build_info.txt"
:: Stage 4: Package
echo [4/4] Packaging...
set "package=%package_dir%\%project%_v!full_version!.zip"
powershell -NoProfile -Command ^
"try { Compress-Archive -Path '%publish_dir%\*' -DestinationPath '!package!' -Force -ErrorAction Stop; exit 0 }" ^
"catch { Write-Host ('[ERROR] ' + $_.Exception.Message); exit 1 }"
if !errorlevel! neq 0 (
echo [FAIL] Packaging failed.
exit /b 1
)
for %%F in ("!package!") do set "size=%%~zF"
echo.
echo =============================================
echo PUBLISH COMPLETE
echo Version: !full_version!
echo Output: %publish_dir%
echo Package: !package!
echo Size: !size! bytes
echo =============================================
pause
Method 5: Multi-Platform Publish
Build for multiple target platforms in one script:
@echo off
setlocal enabledelayedexpansion
set "project=MyToolApp"
set "config=Release"
set "output_base=artifacts"
set "platform_count=3"
set "platforms[0]=win-x64"
set "platforms[1]=linux-x64"
set "platforms[2]=osx-x64"
echo =============================================
echo MULTI-PLATFORM PUBLISH
echo =============================================
echo.
set "success=0"
set "failed=0"
set /a last=platform_count - 1
for /L %%i in (0,1,!last!) do (
set "rid=!platforms[%%i]!"
set "out=%output_base%\!rid!"
echo Building for !rid!...
if exist "!out!" rmdir /s /q "!out!"
dotnet publish "%project%" -c %config% -r !rid! --self-contained ^
/p:PublishSingleFile=true -o "!out!" --verbosity quiet
if !errorlevel!==0 (
set /a success+=1
for %%F in ("!out!\%project%*") do echo [OK] %%~nxF (%%~zF bytes^)
) else (
set /a failed+=1
echo [FAIL]
)
)
echo.
echo =============================================
echo RESULTS: !success! succeeded, !failed! failed
echo =============================================
if !failed! gtr 0 exit /b 1
pause
Docker Publish
Publish and create a Docker image:
@echo off
setlocal
set "project=MyWebAPI"
set "image_name=mywebapi"
set "publish_dir=publish\docker"
set "version=latest"
if exist VERSION (
set /p "version=" < VERSION
)
:: Verify Docker is available
where docker >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Docker not found.
pause
exit /b 1
)
if not exist "Dockerfile" (
echo [ERROR] Dockerfile not found in the current directory.
pause
exit /b 1
)
echo Publishing for Docker...
:: Publish for Linux
if exist "%publish_dir%" rmdir /s /q "%publish_dir%"
dotnet publish "%project%" -c Release -r linux-x64 --self-contained -o "%publish_dir%"
if %errorlevel% neq 0 (
echo [ERROR] dotnet publish failed.
exit /b 1
)
:: Build Docker image
docker build -t %image_name%:%version% .
if %errorlevel%==0 (
echo [SUCCESS] Docker image: %image_name%:%version%
) else (
echo [ERROR] Docker build failed.
exit /b 1
)
pause
Common Mistakes
The Wrong Way: Publishing Debug Configuration
:: WRONG - Debug builds include debug symbols, are not optimized
dotnet publish MyApp -c Debug -o publish
Output Concern:
Debug configuration includes unoptimized code, debug symbols, and potentially development-only settings. Always publish with -c Release for production deployments.
The Wrong Way: Not Specifying Output Directory
:: PROBLEMATIC - Publishes to a deep nested default path
dotnet publish MyApp -c Release
:: Output goes to bin\Release\net8.0\publish\ (hard to find)
Always use -o publish (or similar) to specify a clear, predictable output location.
Best Practices
- Always publish in Release mode:
-c Releaseenables optimizations and excludes debug artifacts. - Specify the output directory: Use
-ofor a predictable deployment source. - Embed version information: Use
/p:Version=x.y.zto stamp assemblies with the release version. - Use self-contained for portability: Include the runtime when the target server's .NET version is uncertain.
- Test after publishing: Run the published output locally before deploying to production.
Conclusion
Publishing a .NET application from a Batch Script is handled by the dotnet publish command, which produces a deployment-ready package from the project source. By choosing between framework-dependent and self-contained modes, enabling single-file publishing for simpler distribution, and embedding version information into the assemblies, teams create professional deployment artifacts. The publish pipeline pattern (clean, test, publish, package) ensures that every published artifact is verified, versioned, and ready for deployment to any target environment.