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:
- Quartz job store - Scheduled jobs, triggers, and scheduler state (
SyRFQuartzdatabase) - MassTransit sagas - Long-running job state machines (
SyRFSagasdatabase)
For local development, you have two options:
- Automatic local Docker containers (recommended) - Zero configuration, complete isolation
- Azure SQL Database - Shared database with schema isolation
Automatic Local Development (Recommended)¶
Prerequisites¶
- Docker Desktop installed and running
- .NET 8.0 SDK
How It Works¶
When you run the Quartz service in Development mode, it automatically:
- Detects your worktree path - Uses the git repository root
- Generates a unique identifier - SHA256 hash of the path (8 hex chars)
- Checks port availability - Verifies the preferred port isn't in use
- Falls back if needed - Automatically finds an alternative port if preferred is occupied
- Creates a Docker container -
syrf-quartz-dev-{hash}with SQL Server 2022 - Creates databases -
SyRFQuartzandSyRFSagas - Configures connection strings - Automatically injects via environment variables
- Displays configuration summary - Shows all ports, connection strings, and management commands
Usage¶
Simply run the Quartz service:
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:
View container logs:
Stop container (data persists):
Remove container (clears data):
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:
Architecture¶
Files Involved¶
| File | Purpose |
|---|---|
| LocalDevelopmentInfrastructure.cs | Docker container management |
| Program.cs | Startup integration |
| appsettings.Development.json | Development configuration |
Port Calculation¶
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