Skip to content

Quartz Local Development Setup

This guide explains how to run the SyRF Quartz service locally with automatic SQL Server provisioning for complete worktree isolation.

Overview

The Quartz service requires SQL Server for:

  1. Quartz job store - Scheduled jobs, triggers, and scheduler state (SyRFQuartz database)
  2. MassTransit sagas - Long-running job state machines (SyRFSagas database)

For local development, you have two options:

  • Automatic local Docker containers (recommended) - Zero configuration, complete isolation
  • Azure SQL Database - Shared database with schema isolation

Prerequisites

  • Docker Desktop installed and running
  • .NET 8.0 SDK

How It Works

When you run the Quartz service in Development mode, it automatically:

  1. Detects your worktree path - Uses the git repository root
  2. Generates a unique identifier - SHA256 hash of the path (8 hex chars)
  3. Checks port availability - Verifies the preferred port isn't in use
  4. Falls back if needed - Automatically finds an alternative port if preferred is occupied
  5. Creates a Docker container - syrf-quartz-dev-{hash} with SQL Server 2022
  6. Creates databases - SyRFQuartz and SyRFSagas
  7. Configures connection strings - Automatically injects via environment variables
  8. Displays configuration summary - Shows all ports, connection strings, and management commands

Usage

Simply run the Quartz service:

cd src/services/quartz/SyRF.Quartz
ASPNETCORE_ENVIRONMENT=Development dotnet run

On first run, you'll see:

Local development mode detected with LOCAL_AUTO - provisioning isolated SQL Server container...
Creating SQL Server container syrf-quartz-dev-a1b2c3d4 on port 14367...
Waiting for SQL Server to be ready on port 14367...
Creating database SyRFQuartz...
Creating database SyRFSagas...
Local SQL Server container ready. Configuration summary:

╔══════════════════════════════════════════════════════════════════════╗
║           LOCAL DEVELOPMENT ENVIRONMENT CONFIGURATION                ║
╠══════════════════════════════════════════════════════════════════════╣
║  Worktree Path:     /home/chris/workspace/syrf                       ║
║  Worktree ID:       a1b2c3d4                                         ║
║  Container Name:    syrf-quartz-dev-a1b2c3d4                         ║
╠══════════════════════════════════════════════════════════════════════╣
║  SQL Server:        localhost:14367                                  ║
║  SA Password:       LocalDev123!                                     ║
╠══════════════════════════════════════════════════════════════════════╣
║  DATABASES                                                           ║
║  ├─ SyRFQuartz:     Job scheduler state (Quartz.NET)                 ║
║  └─ SyRFSagas:      MassTransit saga state machines                  ║
╠══════════════════════════════════════════════════════════════════════╣
║  CONNECTION STRINGS                                                  ║
║  Quartz:   Server=localhost,14367;Database=SyRFQuartz;User Id=sa     ║
║  Sagas:    Server=localhost,14367;Database=SyRFSagas;User Id=sa      ║
╠══════════════════════════════════════════════════════════════════════╣
║  MANAGEMENT COMMANDS                                                 ║
║  View logs:   docker logs syrf-quartz-dev-a1b2c3d4                   ║
║  Stop:        docker stop syrf-quartz-dev-a1b2c3d4                   ║
║  Remove:      docker rm -f syrf-quartz-dev-a1b2c3d4                  ║
╚══════════════════════════════════════════════════════════════════════╝

Subsequent runs reuse the existing container and still show the configuration summary:

Local development mode detected with LOCAL_AUTO - provisioning isolated SQL Server container...
Found existing container syrf-quartz-dev-a1b2c3d4 on port 14367
Local SQL Server container ready. Configuration summary:
...

Worktree Isolation

Each git worktree gets its own isolated container:

Worktree Path Container Name Port
/home/chris/workspace/syrf syrf-quartz-dev-a1b2c3d4 14367
/home/chris/workspace/syrf.pr2247 syrf-quartz-dev-e5f6g7h8 14382
/home/chris/workspace/syrf.feature-x syrf-quartz-dev-1a2b3c4d 14355

This ensures:

  • Different developers don't interfere with each other
  • Different branches/worktrees are completely isolated
  • Schema migrations are independent per worktree

Container Management

View running containers:

docker ps --filter "name=syrf-quartz-dev"

View container logs:

docker logs syrf-quartz-dev-a1b2c3d4

Stop container (data persists):

docker stop syrf-quartz-dev-a1b2c3d4

Remove container (clears data):

docker rm -f syrf-quartz-dev-a1b2c3d4

Connect to SQL Server:

# Using sqlcmd
docker exec -it syrf-quartz-dev-a1b2c3d4 /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -P "LocalDev123!" -C

# Or use Azure Data Studio / SSMS with:
# Server: localhost,14367
# User: sa
# Password: LocalDev123!

Configuration

The automatic provisioning is controlled by appsettings.Development.json:

{
  "ConnectionStrings": {
    "SqlConnection": "LOCAL_AUTO",
    "quartz": "LOCAL_AUTO"
  },
  "LocalDevelopment": {
    "Enabled": true,
    "UseDockerSqlServer": true
  }
}
Setting Description
LOCAL_AUTO Special marker that triggers automatic provisioning
UseDockerSqlServer Must be true for automatic containers

Using Azure SQL Instead

If you prefer to use the shared Azure SQL database (or don't have Docker):

1. Disable Local Provisioning

Override via user secrets:

cd src/services/quartz/SyRF.Quartz

# Set explicit connection strings (overrides LOCAL_AUTO)
dotnet user-secrets set "ConnectionStrings:quartz" "Server=tcp:syrf.database.windows.net,1433;Initial Catalog=syrf-quartz;..."
dotnet user-secrets set "ConnectionStrings:SqlConnection" "Server=tcp:syrf.database.windows.net,1433;Initial Catalog=syrf-sagas;..."

2. Schema Isolation

When using Azure SQL, data is isolated by SQL schema based on runtimeEnvironment:

Environment Schema Tables
Development [development] [development].QRTZ_*, [development].JobSaga
Staging [staging] [staging].QRTZ_*, [staging].JobSaga
Production [production] [production].QRTZ_*, [production].JobSaga
Preview PR [preview_2247] [preview_2247].QRTZ_* (Quartz only)

The runtimeEnvironment defaults to "development" in appsettings.json.

Warning: All local development using Azure SQL shares the same [development] schema. This means:

  • Multiple developers can conflict
  • Different worktrees share the same data

This is why local Docker containers are recommended.

Running Integration Tests

The integration tests use their own Docker container:

cd src/services/quartz/SyRF.Quartz.Tests

# Start the test SQL Server
docker-compose -f docker-compose.integration.yml up -d

# Run integration tests
dotnet test --filter "Category=Integration"

# Stop when done
docker-compose -f docker-compose.integration.yml down

Troubleshooting

Docker Not Running

Failed to provision local SQL Server container.
Ensure Docker is running or provide explicit connection strings in user secrets.

Solution: Start Docker Desktop, or provide explicit Azure SQL connection strings.

Port Conflict

The system automatically handles port conflicts by finding an alternative port:

Preferred port 14367 is in use. Searching for an available port...
Found available port 14368 (attempt 2)
Creating SQL Server container syrf-quartz-dev-a1b2c3d4 on port 14368...

If you need to manually resolve a port conflict:

# Find what's using the port
lsof -i :14367

# Remove the conflicting container
docker rm -f <container-name>

If all 100 ports in the range (14330-14429) are occupied:

Could not find an available port in range 14330-14429.
Please stop some Docker containers or free up ports.

To free up ports, clean up old development containers:

# List all syrf-quartz-dev containers
docker ps -a --filter "name=syrf-quartz-dev"

# Remove unused containers
docker rm -f $(docker ps -a --filter "name=syrf-quartz-dev" -q)

Container Won't Start

# Check container logs
docker logs syrf-quartz-dev-a1b2c3d4

# Common issue: Not enough memory for SQL Server
# Solution: Increase Docker memory limit to at least 2GB

Need to Reset Database

# Remove the container (deletes all data)
docker rm -f syrf-quartz-dev-a1b2c3d4

# Restart the service - fresh container will be created
dotnet run

Check Connection String Being Used

Add logging to see the actual connection string:

export Logging__LogLevel__LocalDevelopmentInfrastructure=Debug
dotnet run

Architecture

Files Involved

File Purpose
LocalDevelopmentInfrastructure.cs Docker container management
Program.cs Startup integration
appsettings.Development.json Development configuration

Port Calculation

Base Port: 14330
Max Offset: 100
Port Range: 14330 - 14429

Port = 14330 + (hash % 100)

This gives ~100 unique ports, sufficient for typical development scenarios.

Container Settings

  • Image: mcr.microsoft.com/mssql/server:2022-latest
  • SA Password: LocalDev123!
  • Restart Policy: unless-stopped (survives Docker restarts)
  • Databases: SyRFQuartz, SyRFSagas