The BookStore API is now complete — it has full CRUD operations, JWT authentication, caching, logging, real-time updates, and a test suite. The final step is deploying it to a production environment so real users can access it. This topic covers deployment to both Windows IIS and Linux using Docker, with Azure as the cloud platform.
Deployment Targets Overview
| Platform | Best For | Complexity |
|---|
| IIS (Windows Server) | Windows-based infrastructure | Low |
| Docker + Linux | Cloud, containers, scalability | Medium |
| Azure App Service | Managed cloud hosting, easy scaling | Low |
| Azure Container Apps | Containerized microservices | Medium |
Step 1 – Prepare for Production
Update appsettings.Production.json
// appsettings.Production.json
{
"ConnectionStrings": {
"BookStoreDB": "Server=prod-sql.database.windows.net;Database=BookStoreDB;User Id=bookstoreuser;Password=StrongPassword123!;"
},
"JwtSettings": {
"Key": "ProductionSuperSecretKeyThatIsAtLeast32CharsLong!",
"Issuer": "BookStoreAPI",
"Audience": "BookStoreClient",
"ExpiresInMinutes": 60
},
"Cors": {
"AllowedOrigins": [ "https://bookstore-frontend.com" ]
},
"Serilog": {
"MinimumLevel": {
"Default": "Warning",
"Override": {
"Microsoft": "Error"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": { "path": "/var/logs/bookstore-.log", "rollingInterval": "Day" }
}
]
}
}
Environment Variable Strategy (Secure)
// Never store secrets in appsettings.json in source control.
// Use environment variables in production:
// Linux:
export ASPNETCORE_ENVIRONMENT=Production
export ConnectionStrings__BookStoreDB="Server=...;..."
export JwtSettings__Key="YourProductionKey"
// Windows:
setx ASPNETCORE_ENVIRONMENT "Production"
Step 2 – Publish the Application
// Self-contained deployment (includes .NET runtime — no runtime needed on server):
dotnet publish -c Release -r linux-x64 --self-contained true -o ./publish
// Framework-dependent deployment (smaller size — requires .NET on server):
dotnet publish -c Release -o ./publish
| Option | Output Size | Requires .NET on Server |
|---|
| Self-contained | Larger (~70MB) | No |
| Framework-dependent | Smaller (~5MB) | Yes |
Option A – Deploy to IIS (Windows)
Prerequisites
- Windows Server with IIS installed
- .NET Hosting Bundle installed (includes the IIS module for ASP.NET Core)
Steps
1. Publish the app:
dotnet publish -c Release -o C:\inetpub\BookStoreAPI
2. In IIS Manager:
- Create a new Site
- Point Physical Path to C:\inetpub\BookStoreAPI
- Set Application Pool to "No Managed Code"
- Assign a port (e.g., 80 or 443)
3. Set environment variable in IIS:
- Application Pool → Advanced Settings → Environment Variables
- Add: ASPNETCORE_ENVIRONMENT = Production
4. Browse to http://yourserver/swagger to verify
web.config (Auto-Generated by publish)
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*"
modules="AspNetCoreModuleV2" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="dotnet"
arguments=".\BookStoreAPI.dll"
stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess"/>
</system.webServer>
</configuration>
Option B – Deploy with Docker (Linux / Cloud)
Create Dockerfile
# Dockerfile
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["BookStoreAPI/BookStoreAPI.csproj", "BookStoreAPI/"]
RUN dotnet restore "BookStoreAPI/BookStoreAPI.csproj"
COPY . .
WORKDIR "/src/BookStoreAPI"
RUN dotnet build -c Release -o /app/build
# Stage 2: Publish
FROM build AS publish
RUN dotnet publish -c Release -o /app/publish
# Stage 3: Final runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
EXPOSE 80
EXPOSE 443
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "BookStoreAPI.dll"]
Create docker-compose.yml (App + Database)
# docker-compose.yml
version: '3.8'
services:
bookstore-api:
build:
context: .
dockerfile: Dockerfile
ports:
- "5000:80"
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__BookStoreDB=Server=sqlserver;Database=BookStoreDB;User Id=sa;Password=YourStrong!Password;TrustServerCertificate=True;
- JwtSettings__Key=ProductionSuperSecretKeyThatIsAtLeast32CharsLong!
depends_on:
- sqlserver
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=YourStrong!Password
ports:
- "1433:1433"
volumes:
- sqlserver_data:/var/opt/mssql
volumes:
sqlserver_data:
Run with Docker Compose
docker-compose up --build -d
# Verify running containers:
docker ps
# Check API logs:
docker logs bookstore-api
# Apply database migrations inside the container:
docker exec bookstore-api dotnet ef database update
Option C – Deploy to Azure App Service
// Step 1: Install Azure CLI
az login
// Step 2: Create a resource group
az group create --name BookStoreRG --location eastus
// Step 3: Create an App Service Plan
az appservice plan create --name BookStorePlan --resource-group BookStoreRG --sku B1 --is-linux
// Step 4: Create the web app
az webapp create --resource-group BookStoreRG --plan BookStorePlan \
--name bookstore-api-prod --runtime "DOTNET|8.0"
// Step 5: Set environment variables in Azure
az webapp config appsettings set --resource-group BookStoreRG \
--name bookstore-api-prod \
--settings ASPNETCORE_ENVIRONMENT=Production \
JwtSettings__Key="ProductionKeyHere"
// Step 6: Deploy via ZIP
dotnet publish -c Release -o ./publish
cd publish
zip -r ../bookstore.zip .
az webapp deploy --resource-group BookStoreRG \
--name bookstore-api-prod \
--src-path ../bookstore.zip
Health Check Endpoint
Add a health check endpoint so load balancers and container orchestrators can verify the API is running:
// Program.cs
builder.Services.AddHealthChecks()
.AddDbContextCheck<BookStoreDbContext>("database");
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var result = new
{
status = report.Status.ToString(),
timestamp = DateTime.UtcNow,
checks = report.Entries.Select(e => new
{
name = e.Key,
status = e.Value.Status.ToString()
})
};
await context.Response.WriteAsJsonAsync(result);
}
});
GET /health
Response: 200 OK
{
"status": "Healthy",
"timestamp": "2024-01-15T10:00:00Z",
"checks": [
{ "name": "database", "status": "Healthy" }
]
}
Production Checklist
| Item | Status |
|---|
| ASPNETCORE_ENVIRONMENT set to Production | Required |
| Swagger UI disabled in Production | Required |
| Secrets in environment variables (not appsettings) | Required |
| HTTPS enforced | Required |
| Database migrations applied | Required |
| Health check endpoint active | Required |
| Logging writing to files or cloud logging | Required |
| CORS configured for production domain only | Required |
| Rate limiting enabled | Recommended |
| JWT expiry appropriate for production | Recommended |
Key Points
- Use environment-specific
appsettings.Production.json and environment variables for production configuration — never hardcode secrets. - Disable Swagger UI in Production to avoid exposing internal API details.
- The
dotnet publish command compiles the API into a deployable folder with all dependencies included. - Docker provides consistent, portable deployments across any Linux server or cloud platform.
- Azure App Service offers managed hosting with easy scaling, environment variable support, and built-in HTTPS.
- A health check endpoint (
/health) allows load balancers and container orchestrators to monitor the API's status automatically.