ssdt-cicd-best-practices-2025

🚨 CRITICAL GUIDELINES

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "ssdt-cicd-best-practices-2025" with this command: npx skills add josiahsiegel/claude-plugin-marketplace/josiahsiegel-claude-plugin-marketplace-ssdt-cicd-best-practices-2025

🚨 CRITICAL GUIDELINES

Windows File Path Requirements

MANDATORY: Always Use Backslashes on Windows for File Paths

When using Edit or Write tools on Windows, you MUST use backslashes (
) in file paths, NOT forward slashes (/ ).

Examples:

  • ❌ WRONG: D:/repos/project/file.tsx

  • βœ… CORRECT: D:\repos\project\file.tsx

This applies to:

  • Edit tool file_path parameter

  • Write tool file_path parameter

  • All file operations on Windows systems

Documentation Guidelines

NEVER create new documentation files unless explicitly requested by the user.

  • Priority: Update existing README.md files rather than creating new documentation

  • Repository cleanliness: Keep repository root clean - only README.md unless user requests otherwise

  • Style: Documentation should be concise, direct, and professional - avoid AI-generated tone

  • User preference: Only create additional .md files when user specifically asks for documentation

SSDT CI/CD Best Practices 2025

Overview

This skill provides comprehensive guidance on implementing modern CI/CD pipelines for SQL Server database projects using SSDT, SqlPackage, and contemporary DevOps practices.

Key Principles (2025 Recommended Approach)

  1. State-Based Deployment (Recommended)

Definition: Source code represents the current database state, not migration scripts.

How it Works:

  • All database objects (tables, procedures, views, functions) stored in separate .sql files

  • SqlPackage automatically generates incremental deployment scripts

  • Declarative approach: "This is what the database should look like"

  • SSDT compares source to target and calculates differences

Advantages:

  • Easier to maintain and understand

  • No risk of missing migration scripts

  • Git history shows complete object definitions

  • Branching and merging simplified

  • Rollback by redeploying previous version

Implementation:

GitHub Actions example

  • name: Build DACPAC (State-Based) run: dotnet build Database.sqlproj -c Release

  • name: Deploy State to Target run: | sqlpackage /Action:Publish
    /SourceFile:Database.dacpac
    /TargetConnectionString:"${{ secrets.SQL_CONN }}"
    /p:BlockOnPossibleDataLoss=True

Contrast with Migration-Based:

  • Migration-based: Sequential scripts (001_CreateTable.sql, 002_AddColumn.sql)

  • State-based: Object definitions (Tables/Customer.sql contains complete CREATE TABLE)

  1. tSQLt Unit Testing (Critical for CI/CD)

Why tSQLt:

  • Open-source SQL Server unit testing framework

  • Write tests in T-SQL language

  • Produces machine-readable XML/JSON results

  • Integrates seamlessly with CI/CD pipelines

Key Features:

  • Automatic Transactions: Each test runs in a transaction and rolls back

  • Schema Grouping: Group related tests in schemas

  • Mocking: Fake tables and procedures for isolated testing

  • Assertions: Built-in assertion methods (assertEquals, assertEmpty, etc.)

Pipeline Abort on Failure:

GitHub Actions with tSQLt

  • name: Run tSQLt Unit Tests run: |

    Deploy test framework

    sqlpackage /Action:Publish
    /SourceFile:DatabaseTests.dacpac
    /TargetConnectionString:"${{ secrets.TEST_SQL_CONN }}"

    Execute tests and capture results

    sqlcmd -S test-server -d TestDB -Q "EXEC tSQLt.RunAll" -o test-results.txt

    Parse results and fail pipeline if tests fail

    if grep -q "Failure" test-results.txt; then echo "Unit tests failed!" exit 1 fi

    echo "All tests passed!"

  • name: Deploy to Production (only runs if tests pass) run: | sqlpackage /Action:Publish
    /SourceFile:Database.dacpac
    /TargetConnectionString:"${{ secrets.PROD_SQL_CONN }}"

Test Structure:

-- tSQLt test example CREATE SCHEMA CustomerTests; GO

CREATE PROCEDURE CustomerTests.[test Customer Insert Sets Correct Defaults] AS BEGIN -- Arrange EXEC tSQLt.FakeTable 'dbo.Customers';

-- Act
INSERT INTO dbo.Customers (FirstName, LastName, Email)
VALUES ('John', 'Doe', 'john@example.com');

-- Assert
EXEC tSQLt.AssertEquals @Expected = 1,
                         @Actual = (SELECT COUNT(*) FROM dbo.Customers);
EXEC tSQLt.AssertNotEquals @Expected = NULL,
                            @Actual = (SELECT CreatedDate FROM dbo.Customers);

END; GO

-- Run all tests EXEC tSQLt.RunAll;

Azure DevOps Integration:

  • task: PowerShell@2 displayName: 'Run tSQLt Tests' inputs: targetType: 'inline' script: | # Execute tSQLt tests $results = Invoke-Sqlcmd -ServerInstance $(testServer) -Database $(testDatabase) -Query "EXEC tSQLt.RunAll" ` -Verbose

    # Check for failures
    $failures = $results | Where-Object { $_.Class -eq 'Failure' }
    if ($failures) {
      Write-Error "Tests failed: $($failures.Count) failures"
      exit 1
    }
    
  1. Windows Authentication Over SQL Authentication

Security Best Practice: Prefer Windows Authentication (Integrated Security) for CI/CD agents.

Why Windows Auth:

  • No passwords stored in connection strings

  • Leverages existing Active Directory infrastructure

  • Service accounts with minimal permissions

  • Audit trail via Windows Security logs

  • No credential rotation needed

Implementation:

Self-Hosted Agents (Recommended):

GitHub Actions with self-hosted Windows agent

runs-on: [self-hosted, windows, sql-deploy]

steps:

  • name: Deploy with Windows Auth run: | sqlpackage /Action:Publish
    /SourceFile:Database.dacpac
    /TargetConnectionString:"Server=prod-sql;Database=MyDB;Integrated Security=True;"
    /p:BlockOnPossibleDataLoss=True

Azure DevOps with Service Connection:

  • task: SqlAzureDacpacDeployment@1 inputs: authenticationType: 'integratedAuth' # Uses Windows Auth serverName: 'prod-sql.domain.com' databaseName: 'MyDatabase' dacpacFile: '$(Build.ArtifactStagingDirectory)/Database.dacpac'

Alternative for Cloud Agents (Azure SQL):

Use Managed Identity instead of SQL auth

  • name: Deploy with Managed Identity run: | sqlpackage /Action:Publish
    /SourceFile:Database.dacpac
    /TargetConnectionString:"Server=tcp:server.database.windows.net;Database=MyDB;Authentication=ActiveDirectoryManagedIdentity;"
    /p:BlockOnPossibleDataLoss=True

Never Do This:

BAD: Plain text SQL auth password

TargetConnectionString: "Server=prod;Database=MyDB;User=sa;Password=P@ssw0rd123"

If SQL Auth Required:

Use secrets/variables (least preferred method)

  • name: Deploy with SQL Auth (Not Recommended) run: | sqlpackage /Action:Publish
    /SourceFile:Database.dacpac
    /TargetServerName:"${{ secrets.SQL_SERVER }}"
    /TargetDatabaseName:"${{ secrets.SQL_DATABASE }}"
    /TargetUser:"${{ secrets.SQL_USER }}"
    /TargetPassword:"${{ secrets.SQL_PASSWORD }}"
    /p:BlockOnPossibleDataLoss=True

    Still not as secure as Windows Auth!

  1. Version Control Everything

What to Version:

DatabaseProject/ β”œβ”€β”€ Tables/ β”‚ β”œβ”€β”€ Customer.sql β”‚ └── Order.sql β”œβ”€β”€ StoredProcedures/ β”‚ └── GetCustomerOrders.sql β”œβ”€β”€ Tests/ β”‚ β”œβ”€β”€ CustomerTests/ β”‚ β”‚ └── test_CustomerInsert.sql β”‚ └── OrderTests/ β”‚ └── test_OrderValidation.sql β”œβ”€β”€ Scripts/ β”‚ β”œβ”€β”€ Script.PreDeployment.sql β”‚ └── Script.PostDeployment.sql β”œβ”€β”€ Database.sqlproj β”œβ”€β”€ Database.Dev.publish.xml β”œβ”€β”€ Database.Prod.publish.xml └── .gitignore

.gitignore:

Build outputs

bin/ obj/ *.dacpac

User-specific files

*.user *.suo

Visual Studio folders

.vs/

Never commit credentials

*.publish.xml.user

Check-in Requirements:

  • Require code review for database changes

  • Mandate comments on all commits

  • Run automated tests before merge

  • Enforce naming conventions via branch policies

  1. Deployment Reports Always Required

Before Production Deployment:

  • name: Generate Deployment Report run: | sqlpackage /Action:DeployReport
    /SourceFile:Database.dacpac
    /TargetConnectionString:"${{ secrets.PROD_SQL_CONN }}"
    /OutputPath:deploy-report.xml
    /p:BlockOnPossibleDataLoss=True

  • name: Parse and Review Report run: |

    Extract key metrics from XML

    echo "=== DEPLOYMENT REPORT ==="

    Parse XML for operations count

    Check for data loss warnings

    Display to user or post to PR

  • name: Require Manual Approval uses: trstringer/manual-approval@v1 with: approvers: database-admins minimum-approvals: 1 instructions: "Review deploy-report.xml before approving"

  • name: Deploy After Approval run: | sqlpackage /Action:Publish
    /SourceFile:Database.dacpac
    /TargetConnectionString:"${{ secrets.PROD_SQL_CONN }}"

  1. Environment Promotion Strategy

Standard Flow: Dev β†’ QA β†’ Staging β†’ Production

Consistent Deployment Options:

Define environment-specific properties

environments: dev: blockOnDataLoss: false dropObjectsNotInSource: true backupBeforeChanges: false

qa: blockOnDataLoss: true dropObjectsNotInSource: false backupBeforeChanges: true

staging: blockOnDataLoss: true dropObjectsNotInSource: false backupBeforeChanges: true

production: blockOnDataLoss: true dropObjectsNotInSource: false backupBeforeChanges: true requireApproval: true

Complete GitHub Actions Pipeline (2025 Best Practice)

name: SQL Server CI/CD Pipeline

on: push: branches: [main, develop] pull_request: branches: [main]

env: DOTNET_VERSION: '8.0.x' SQLPACKAGE_VERSION: '170.2.70'

jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4

  - name: Setup .NET 8
    uses: actions/setup-dotnet@v4
    with:
      dotnet-version: ${{ env.DOTNET_VERSION }}

  - name: Install SqlPackage
    run: dotnet tool install -g Microsoft.SqlPackage --version ${{ env.SQLPACKAGE_VERSION }}

  - name: Build Database Project
    run: dotnet build src/Database.sqlproj -c Release

  - name: Build Test Project
    run: dotnet build tests/DatabaseTests.sqlproj -c Release

  - name: Upload DACPAC Artifacts
    uses: actions/upload-artifact@v4
    with:
      name: dacpacs
      path: |
        src/bin/Release/*.dacpac
        tests/bin/Release/*.dacpac

test: runs-on: windows-latest # tSQLt requires SQL Server needs: build steps: - uses: actions/checkout@v4

  - name: Download Artifacts
    uses: actions/download-artifact@v4
    with:
      name: dacpacs

  - name: Setup Test Database
    run: |
      sqlcmd -S localhost -Q "CREATE DATABASE TestDB"

  - name: Deploy Database to Test
    run: |
      sqlpackage /Action:Publish `
        /SourceFile:Database.dacpac `
        /TargetConnectionString:"Server=localhost;Database=TestDB;Integrated Security=True;"

  - name: Deploy tSQLt Framework
    run: |
      sqlpackage /Action:Publish `
        /SourceFile:DatabaseTests.dacpac `
        /TargetConnectionString:"Server=localhost;Database=TestDB;Integrated Security=True;"

  - name: Run tSQLt Unit Tests
    run: |
      $results = Invoke-Sqlcmd -ServerInstance localhost `
                                -Database TestDB `
                                -Query "EXEC tSQLt.RunAll" `
                                -Verbose

      $failures = $results | Where-Object { $_.Class -eq 'Failure' }
      if ($failures) {
        Write-Error "Tests failed: $($failures.Count) failures"
        exit 1
      }
      Write-Host "All tests passed!"

deploy-dev: runs-on: [self-hosted, windows, sql-deploy] needs: test if: github.ref == 'refs/heads/develop' environment: dev steps: - name: Download Artifacts uses: actions/download-artifact@v4 with: name: dacpacs

  - name: Deploy to Dev (Windows Auth)
    run: |
      sqlpackage /Action:Publish `
        /SourceFile:Database.dacpac `
        /TargetConnectionString:"Server=dev-sql;Database=MyDB;Integrated Security=True;" `
        /p:BlockOnPossibleDataLoss=False `
        /p:DropObjectsNotInSource=True

deploy-staging: runs-on: [self-hosted, windows, sql-deploy] needs: test if: github.ref == 'refs/heads/main' environment: staging steps: - name: Download Artifacts uses: actions/download-artifact@v4 with: name: dacpacs

  - name: Generate Deployment Report
    run: |
      sqlpackage /Action:DeployReport `
        /SourceFile:Database.dacpac `
        /TargetConnectionString:"Server=staging-sql;Database=MyDB;Integrated Security=True;" `
        /OutputPath:deploy-report.xml

  - name: Deploy to Staging (Windows Auth)
    run: |
      sqlpackage /Action:Publish `
        /SourceFile:Database.dacpac `
        /TargetConnectionString:"Server=staging-sql;Database=MyDB;Integrated Security=True;" `
        /p:BlockOnPossibleDataLoss=True `
        /p:BackupDatabaseBeforeChanges=True `
        /p:DropObjectsNotInSource=False

deploy-production: runs-on: [self-hosted, windows, sql-deploy] needs: deploy-staging environment: production steps: - name: Download Artifacts uses: actions/download-artifact@v4 with: name: dacpacs

  - name: Generate Deployment Report
    run: |
      sqlpackage /Action:DeployReport `
        /SourceFile:Database.dacpac `
        /TargetConnectionString:"Server=prod-sql;Database=MyDB;Integrated Security=True;" `
        /OutputPath:prod-deploy-report.xml

  - name: Manual Approval Required
    uses: trstringer/manual-approval@v1
    with:
      approvers: database-admins,devops-leads
      minimum-approvals: 2
      instructions: "Review prod-deploy-report.xml and approve deployment"

  - name: Deploy to Production (Windows Auth)
    run: |
      sqlpackage /Action:Publish `
        /SourceFile:Database.dacpac `
        /TargetConnectionString:"Server=prod-sql;Database=MyDB;Integrated Security=True;" `
        /p:BlockOnPossibleDataLoss=True `
        /p:BackupDatabaseBeforeChanges=True `
        /p:DropObjectsNotInSource=False `
        /p:DoNotDropObjectTypes=Users;Logins;RoleMembership `
        /DiagnosticsFile:prod-deploy.log

  - name: Upload Deployment Logs
    if: always()
    uses: actions/upload-artifact@v4
    with:
      name: production-deployment-logs
      path: prod-deploy.log

Azure DevOps Pipeline Example (2025)

trigger: branches: include: - main - develop

pool: vmImage: 'windows-2022'

variables: buildConfiguration: 'Release' dotnetVersion: '8.0.x' sqlPackageVersion: '170.2.70'

stages:

  • stage: Build jobs:

    • job: BuildDatabase steps:
      • task: UseDotNet@2 displayName: 'Install .NET 8' inputs: version: $(dotnetVersion)

      • task: DotNetCoreCLI@2 displayName: 'Build Database Project' inputs: command: 'build' projects: '**/*.sqlproj' arguments: '-c $(buildConfiguration)'

      • task: PublishBuildArtifacts@1 displayName: 'Publish DACPAC' inputs: PathtoPublish: '$(Build.SourcesDirectory)/bin/$(buildConfiguration)' ArtifactName: 'dacpacs'

  • stage: Test dependsOn: Build jobs:

    • job: RunUnitTests steps:
      • task: DownloadBuildArtifacts@1 inputs: artifactName: 'dacpacs'

      • task: SqlAzureDacpacDeployment@1 displayName: 'Deploy to Test Database' inputs: authenticationType: 'integratedAuth' serverName: 'test-sql-server' databaseName: 'TestDB' dacpacFile: '$(System.ArtifactsDirectory)/dacpacs/Database.dacpac'

      • task: PowerShell@2 displayName: 'Run tSQLt Tests' inputs: targetType: 'inline' script: | $results = Invoke-Sqlcmd -ServerInstance 'test-sql-server' -Database 'TestDB' -Query "EXEC tSQLt.RunAll"

        $failures = $results | Where-Object { $_.Class -eq 'Failure' }
        if ($failures) {
          throw "Tests failed: $($failures.Count) failures"
        }
        
  • stage: DeployProduction dependsOn: Test condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) jobs:

    • deployment: DeployToProduction environment: 'Production' strategy: runOnce: deploy: steps: - task: SqlAzureDacpacDeployment@1 displayName: 'Generate Deployment Report' inputs: deployType: 'DeployReport' authenticationType: 'integratedAuth' serverName: 'prod-sql-server' databaseName: 'ProductionDB' dacpacFile: '$(Pipeline.Workspace)/dacpacs/Database.dacpac' outputFile: 'deploy-report.xml'

        - task: SqlAzureDacpacDeployment@1
          displayName: 'Deploy to Production'
          inputs:
            authenticationType: 'integratedAuth'
            serverName: 'prod-sql-server'
            databaseName: 'ProductionDB'
            dacpacFile: '$(Pipeline.Workspace)/dacpacs/Database.dacpac'
            additionalArguments: '/p:BlockOnPossibleDataLoss=True /p:BackupDatabaseBeforeChanges=True'
      

Best Practices Checklist

Source Control

  • All database objects in source control

  • .gitignore configured for build outputs

  • No credentials committed

  • Test scripts versioned separately

  • Branching strategy defined (gitflow, trunk-based, etc.)

Testing

  • tSQLt framework deployed

  • Unit tests cover critical stored procedures

  • Tests grouped logically in schemas

  • Pipeline aborts on test failure

  • Test results published to dashboard

Security

  • Windows Authentication used for CI/CD

  • Service accounts follow principle of least privilege

  • Secrets stored in Azure Key Vault / GitHub Secrets

  • No plain text passwords

  • Audit logging enabled

Deployment

  • State-based deployment strategy

  • Deployment reports generated before production

  • Manual approval gates for production

  • Backup before changes (production)

  • BlockOnPossibleDataLoss enabled (production)

  • DoNotDropObjectTypes configured

  • Rollback plan documented

Monitoring

  • Deployment logs captured

  • Failed deployments trigger alerts

  • Performance metrics tracked

  • Schema drift detection automated

Resources

  • tSQLt Official Site

  • Microsoft.Build.Sql Documentation

  • SqlPackage Reference

  • Azure DevOps SQL Tasks

  • GitHub Actions for SQL

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

tailwindcss-advanced-layouts

No summary provided by upstream source.

Repository SourceNeeds Review
General

tailwindcss-animations

No summary provided by upstream source.

Repository SourceNeeds Review
General

tailwindcss-mobile-first

No summary provided by upstream source.

Repository SourceNeeds Review
General

docker-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review