Skip to content

Deployment

SimpleModule applications deploy as standard ASP.NET applications. This guide covers Docker deployment, the CI/CD pipeline, and production configuration.

Docker

Dockerfile

The project includes a multi-stage Dockerfile that produces a minimal runtime image:

dockerfile
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# Copy project files and restore (layer caching)
COPY Directory.Build.props Directory.Packages.props ./
COPY *.slnx ./
COPY framework/SimpleModule.Core/*.csproj framework/SimpleModule.Core/
COPY framework/SimpleModule.Database/*.csproj framework/SimpleModule.Database/
COPY framework/SimpleModule.Generator/*.csproj framework/SimpleModule.Generator/
COPY framework/SimpleModule.Blazor/*.csproj framework/SimpleModule.Blazor/
COPY template/SimpleModule.Host/*.csproj template/SimpleModule.Host/
# ... module project files ...
RUN dotnet restore template/SimpleModule.Host/SimpleModule.Host.csproj

# Build and publish
COPY . .
RUN dotnet publish template/SimpleModule.Host/SimpleModule.Host.csproj \
    -c Release -o /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "SimpleModule.Host.dll"]

Key points:

  • Multi-stage build -- the SDK image is only used for building; the final image uses the smaller aspnet runtime image
  • Layer caching -- project files are copied and restored before the full source copy, so NuGet restore is cached across builds
  • The runtime container exposes port 8080

Docker Compose

For local testing or simple deployments, use docker-compose.yml:

yaml
services:
  api:
    build: .
    ports:
      - "8080:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - Database__DefaultConnection=Host=postgres;Database=simplemodule;Username=simplemodule;Password=simplemodule
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: simplemodule
      POSTGRES_PASSWORD: simplemodule
      POSTGRES_DB: simplemodule
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U simplemodule"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  pgdata:

Start with:

bash
docker compose up -d

The API service waits for PostgreSQL to pass its health check before starting.

CI/CD Pipeline

The GitHub Actions workflow (.github/workflows/ci.yml) runs on every push to main and every pull request. It has four jobs:

1. Build

build → Restore → Build

Verifies the solution compiles without errors.

2. Test (SQLite)

test-sqlite → Run all tests with in-memory SQLite

Runs the full test suite using SQLite (the default provider). This is fast and catches most issues.

3. Test (PostgreSQL)

test-postgresql → Run all tests against PostgreSQL service container

Runs the same test suite against a real PostgreSQL 16 instance. This catches provider-specific issues like SQL dialect differences. The job also verifies that EF Core migrations are up to date:

bash
dotnet ef migrations has-pending-model-changes

The connection string is passed via environment variable:

Database__DefaultConnection=Host=localhost;Database=simplemodule_test;Username=test;Password=test

4. Publish

publish → dotnet publish -c Release (only on main branch)

Runs only when tests pass on the main branch. Produces the release artifacts.

Pipeline Diagram

build ──┬── test-sqlite ────┬── publish (main only)
        └── test-postgresql ─┘

The test jobs run in parallel after build succeeds. Publish runs after both test jobs pass.

Production Build

Build a release-optimized application with:

bash
dotnet publish template/SimpleModule.Host/SimpleModule.Host.csproj -c Release

This produces a self-contained deployment package in the default publish directory.

Production Database

PostgreSQL is the recommended database for production. Configure it via appsettings.json or environment variables:

Using appsettings.json

json
{
  "Database": {
    "DefaultConnection": "Host=your-server;Database=simplemodule;Username=app;Password=secret",
    "Provider": "PostgreSQL"
  }
}

Using Environment Variables

Environment variables override appsettings.json. Use the __ (double underscore) separator for nested keys:

bash
export Database__DefaultConnection="Host=your-server;Database=simplemodule;Username=app;Password=secret"
export Database__Provider="PostgreSQL"

WARNING

For production, always use environment variables or a secrets manager for connection strings. Never commit credentials to source control.

Environment Configuration

Key Environment Variables

VariableDescriptionExample
ASPNETCORE_ENVIRONMENTRuntime environmentProduction, Development
Database__DefaultConnectionDatabase connection stringHost=...;Database=...
Database__ProviderDatabase providerSqlite, PostgreSQL

Docker Environment

When running in Docker, pass environment variables via docker-compose.yml or docker run:

bash
docker run -d \
  -p 8080:8080 \
  -e ASPNETCORE_ENVIRONMENT=Production \
  -e Database__DefaultConnection="Host=db;Database=simplemodule;Username=app;Password=secret" \
  simplemodule

Database Initialization

SimpleModule uses EnsureCreated() by default, which creates the database schema if it does not exist. For production environments with evolving schemas, use EF Core migrations per module:

bash
# Add a migration for a specific module's DbContext
dotnet ef migrations add InitialCreate \
  --project modules/Products/src/Products \
  --startup-project template/SimpleModule.Host

# Apply migrations
dotnet ef database update \
  --project modules/Products/src/Products \
  --startup-project template/SimpleModule.Host

TIP

Each module has its own DbContext and schema. In PostgreSQL, modules use separate schemas for isolation. In SQLite, modules use table prefixes.

Next Steps

Released under the MIT License.