> ## Documentation Index
> Fetch the complete documentation index at: https://www.pgschema.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GitOps

pgschema integrates seamlessly with GitOps workflows, providing a declarative approach to database schema management that fits naturally into your existing CI/CD pipelines.
The typical workflow follows a review-then-apply pattern that ensures all schema changes are properly reviewed before being applied to production.

<Steps>
  <Step title="Development Phase">
    * Developers modify schema files in their feature branch
    * Schema changes are committed alongside application code
    * The desired state is expressed declaratively in SQL files
  </Step>

  <Step title="Pull/Merge Request Review">
    * When a PR/MR is opened, CI automatically runs [pgschema plan](/cli/plan)
    * The plan output shows exactly what changes will be applied
    * The plan is posted as a comment on the PR/MR for review
    * Reviewers can see:
      * What tables, indexes, functions will be added/modified/dropped
      * The exact SQL statements that will be executed
      * Whether changes can run in a transaction
      * Any potentially destructive operations
  </Step>

  <Step title="Approval and Merge">
    * Once reviewers approve the schema changes
    * The PR/MR is merged to the target branch (e.g., main, production)
  </Step>

  <Step title="Deployment Phase">
    * After merge, CD pipeline automatically runs [pgschema apply](/cli/apply)
    * The apply command uses the reviewed plan to execute migrations
    * Schema changes are applied to the target database
    * The deployment includes safety checks for concurrent modifications
  </Step>
</Steps>

## GitHub Actions

<Card title="GitHub Actions Example Repository" icon="github" href="https://github.com/pgschema/github-actions-example">
  Complete example repository with workflows and sample PRs
</Card>

**Single-file Schema Workflow <Icon icon="file-code" size={20} />**

<CardGroup cols={3}>
  <Card title="Plan GitHub Actions" icon="magnifying-glass" href="https://github.com/pgschema/github-actions-example/blob/main/.github/workflows/pgschema-singlefile-plan.yml">
    Generates migration plan on pull requests
  </Card>

  <Card title="Apply GitHub Actions" icon="play" href="https://github.com/pgschema/github-actions-example/blob/main/.github/workflows/pgschema-singlefile-apply.yml">
    Applies migrations after merge
  </Card>

  <Card title="Example PR" icon="code-pull-request" href="https://github.com/pgschema/github-actions-example/pull/12">
    Live example with plan comments
  </Card>
</CardGroup>

**Modular Multi-file Schema Workflow <Icon icon="folder-tree" size={20} />**

<CardGroup cols={3}>
  <Card title="Plan GitHub Actions" icon="magnifying-glass" href="https://github.com/pgschema/github-actions-example/blob/main/.github/workflows/pgschema-multifile-plan.yml">
    Handles modular schema files
  </Card>

  <Card title="Apply GitHub Actions" icon="play" href="https://github.com/pgschema/github-actions-example/blob/main/.github/workflows/pgschema-multifile-apply.yml">
    Applies modular migrations
  </Card>

  <Card title="Example PR" icon="code-pull-request" href="https://github.com/pgschema/github-actions-example/pull/13">
    Multi-file schema changes
  </Card>
</CardGroup>

**Example GitHub Actions Workflows**

<Tabs>
  <Tab title="Plan Workflow">
    ```yaml .github/workflows/pgschema-plan.yml theme={null}
    name: Schema Migration Plan

    on:
      pull_request:
        paths:
          - 'schema/schema.sql'

    jobs:
      plan:
        runs-on: ubuntu-latest
        
        steps:
          - uses: actions/checkout@v4
            with:
              fetch-depth: 0
          
          - uses: actions/setup-go@v5
            with:
              go-version: '1.24'
          
          - name: Install pgschema
            run: go install github.com/pgplex/pgschema@latest
          
          - name: Generate migration plan
            env:
              PGPASSWORD: ${{ secrets.DB_PASSWORD }}
            run: |
              pgschema plan \
                --host ${{ secrets.DB_HOST }} \
                --port ${{ secrets.DB_PORT }} \
                --db ${{ secrets.DB_NAME }} \
                --user ${{ secrets.DB_USER }} \
                --file schema/schema.sql \
                --output-json plan.json \
                --output-human plan.txt
          
          - name: Comment PR with plan
            uses: actions/github-script@v7
            with:
              script: |
                const fs = require('fs');
                const planContent = fs.readFileSync('plan.txt', 'utf8');
                
                // Find existing comment
                const { data: comments } = await github.rest.issues.listComments({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: context.issue.number,
                });
                
                const botComment = comments.find(comment => 
                  comment.user.type === 'Bot' && 
                  comment.body.includes('## Migration Plan')
                );
                
                const body = `## Migration Plan\n\`\`\`sql\n${planContent}\n\`\`\``;
                
                if (botComment) {
                  await github.rest.issues.updateComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    comment_id: botComment.id,
                    body
                  });
                } else {
                  await github.rest.issues.createComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    issue_number: context.issue.number,
                    body
                  });
                }
          
          - name: Upload plan artifact
            uses: actions/upload-artifact@v4
            with:
              name: pgschema-plan-${{ github.event.pull_request.number }}
              path: plan.json
              retention-days: 30
    ```
  </Tab>

  <Tab title="Apply Workflow">
    ```yaml .github/workflows/pgschema-apply.yml theme={null}
    name: Apply Schema Migration

    on:
      pull_request:
        types: [closed]
        branches:
          - main
        paths:
          - 'schema/schema.sql'

    jobs:
      apply:
        runs-on: ubuntu-latest
        if: github.event.pull_request.merged == true
        environment: production
        
        steps:
          - uses: actions/checkout@v4
          
          - uses: actions/setup-go@v5
            with:
              go-version: '1.24'
          
          - name: Install pgschema
            run: go install github.com/pgplex/pgschema@latest
          
          - name: Download plan artifact
            uses: dawidd6/action-download-artifact@v6
            with:
              workflow: pgschema-plan.yml
              pr: ${{ github.event.pull_request.number }}
              name: pgschema-plan-${{ github.event.pull_request.number }}
              path: .
          
          - name: Validate plan exists
            run: |
              if [ ! -f "plan.json" ]; then
                echo "Error: plan.json not found"
                exit 1
              fi
          
          - name: Apply migration
            id: apply
            env:
              PGPASSWORD: ${{ secrets.DB_PASSWORD }}
            run: |
              echo "Applying migration plan from PR #${{ github.event.pull_request.number }}"
              
              # Capture apply output
              APPLY_OUTPUT=$(pgschema apply \
                --host ${{ secrets.DB_HOST }} \
                --port ${{ secrets.DB_PORT }} \
                --db ${{ secrets.DB_NAME }} \
                --user ${{ secrets.DB_USER }} \
                --plan plan.json \
                --auto-approve \
                2>&1)
              
              APPLY_EXIT_CODE=$?
              
              echo "Apply output:"
              echo "$APPLY_OUTPUT"
              
              # Set output for the next step
              echo "output<<EOF" >> $GITHUB_OUTPUT
              echo "$APPLY_OUTPUT" >> $GITHUB_OUTPUT
              echo "EOF" >> $GITHUB_OUTPUT
              
              exit $APPLY_EXIT_CODE
          
          - name: Comment on PR
            uses: actions/github-script@v7
            with:
              script: |
                await github.rest.issues.createComment({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: context.issue.number,
                  body: '✅ Schema migration has been successfully applied to production!'
                });
    ```
  </Tab>
</Tabs>

## GitLab CI

Demonstrates a complete workflow for reviewing and applying schema changes through merge requests.

```yaml .gitlab-ci.yml theme={null}
stages:
  - plan
  - apply

variables:
  PGPASSWORD: $DB_PASSWORD

# Plan job - runs on merge requests and main branch
pgschema-plan:
  stage: plan
  image: golang:1.24
  script:
    - go install github.com/pgplex/pgschema@latest
    - |
      pgschema plan \
        --host $DB_HOST \
        --port ${DB_PORT:-5432} \
        --db $DB_NAME \
        --user $DB_USER \
        --file schema/schema.sql \
        --output-json plan.json \
        --output-human plan.txt
    - cat plan.txt
    
    # Post plan as MR comment (only for merge requests)
    - |
      if [ -n "$CI_MERGE_REQUEST_IID" ]; then
        PLAN_CONTENT=$(cat plan.txt)
        curl --request POST \
          --header "PRIVATE-TOKEN: $CI_JOB_TOKEN" \
          --data-urlencode "body=## Migration Plan\n\`\`\`\n$PLAN_CONTENT\n\`\`\`" \
          "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
      fi
  
  artifacts:
    paths:
      - plan.json
      - plan.txt
    expire_in: 7 days
  
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      changes:
        - schema/**
    - if: '$CI_COMMIT_BRANCH == "main"'
      changes:
        - schema/**

# Apply job - manual trigger on main branch
pgschema-apply:
  stage: apply
  image: golang:1.24
  needs: ["pgschema-plan"]
  dependencies:
    - pgschema-plan
  
  script:
    - go install github.com/pgplex/pgschema@latest
    - |
      if [ ! -f plan.json ]; then
        echo "Error: plan.json not found"
        exit 1
      fi
    - |
      pgschema apply \
        --host $DB_HOST \
        --port ${DB_PORT:-5432} \
        --db $DB_NAME \
        --user $DB_USER \
        --plan plan.json \
        --auto-approve
    - echo "✅ Schema migration applied successfully"
  
  when: manual  # Require manual approval
  environment:
    name: production
  
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      changes:
        - schema/**
```

## Azure Pipelines

Demonstrates a complete workflow for reviewing and applying schema changes through pull requests.

```yaml azure-pipelines.yml theme={null}
trigger:
  branches:
    include:
      - main
  paths:
    include:
      - schema/*

pr:
  branches:
    include:
      - main
  paths:
    include:
      - schema/*

variables:
  PGPASSWORD: $(DB_PASSWORD)

stages:
  - stage: Plan
    displayName: 'Generate Migration Plan'
    jobs:
      - job: PlanJob
        displayName: 'Run pgschema plan'
        pool:
          vmImage: 'ubuntu-latest'
        
        steps:
          - task: GoTool@0
            inputs:
              version: '1.24'
          
          - script: |
              go install github.com/pgplex/pgschema@latest
            displayName: 'Install pgschema'
          
          - script: |
              pgschema plan \
                --host $(DB_HOST) \
                --port $(DB_PORT) \
                --db $(DB_NAME) \
                --user $(DB_USER) \
                --file schema/schema.sql \
                --output-json plan.json \
                --output-human plan.txt
            displayName: 'Generate plan'
          
          - script: cat plan.txt
            displayName: 'Display plan'
          
          # Post plan as PR comment (for PRs only)
          - task: GitHubComment@0
            condition: eq(variables['Build.Reason'], 'PullRequest')
            inputs:
              gitHubConnection: 'github-connection'
              repositoryName: '$(Build.Repository.Name)'
              comment: |
                ## Migration Plan
                <pre>$(cat plan.txt)</pre>
          
          - publish: plan.json
            artifact: migration-plan
            displayName: 'Publish plan artifact'

  - stage: Apply
    displayName: 'Apply Migration'
    dependsOn: Plan
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: ApplyJob
        displayName: 'Apply pgschema migration'
        pool:
          vmImage: 'ubuntu-latest'
        environment: 'production'
        strategy:
          runOnce:
            deploy:
              steps:
                - task: GoTool@0
                  inputs:
                    version: '1.24'
                
                - script: |
                    go install github.com/pgplex/pgschema@latest
                  displayName: 'Install pgschema'
                
                - download: current
                  artifact: migration-plan
                  displayName: 'Download plan artifact'
                
                - script: |
                    if [ ! -f $(Pipeline.Workspace)/migration-plan/plan.json ]; then
                      echo "Error: plan.json not found"
                      exit 1
                    fi
                  displayName: 'Verify plan exists'
                
                - script: |
                    pgschema apply \
                      --host $(DB_HOST) \
                      --port $(DB_PORT) \
                      --db $(DB_NAME) \
                      --user $(DB_USER) \
                      --plan $(Pipeline.Workspace)/migration-plan/plan.json \
                      --auto-approve
                  displayName: 'Apply migration'
                
                - script: echo "✅ Schema migration applied successfully"
                  displayName: 'Report success'
```
