Skip to main content

CI/CD Integration

Add Treeship attestations to your CI/CD pipelines to create verifiable records of builds, tests, and deployments.

Why CI/CD Attestation?

BenefitDescription
Deployment receiptsCryptographic proof of what was deployed
Audit trailComplete history of all pipeline runs
AccountabilityKnow which agent/process made changes
ComplianceMeet audit requirements with verifiable records

GitHub Actions

Basic Deployment Attestation

name: Deploy and Attest

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to production
        run: |
          # Your deployment command
          railway up --service my-app
          
      - name: Create attestation
        env:
          TREESHIP_API_KEY: ${{ secrets.TREESHIP_API_KEY }}
        run: |
          curl -X POST https://api.treeship.dev/v1/attest \
            -H "Authorization: Bearer $TREESHIP_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
              "agent_slug": "github-actions",
              "action": "Deployed main branch to production",
              "inputs_hash": "${{ github.sha }}",
              "metadata": {
                "commit": "${{ github.sha }}",
                "branch": "${{ github.ref_name }}",
                "actor": "${{ github.actor }}",
                "run_id": "${{ github.run_id }}"
              }
            }'

Test Results Attestation

name: Test and Attest

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run tests
        id: tests
        run: |
          npm test 2>&1 | tee test-output.txt
          echo "status=$?" >> $GITHUB_OUTPUT
          
      - name: Create test attestation
        if: always()
        env:
          TREESHIP_API_KEY: ${{ secrets.TREESHIP_API_KEY }}
        run: |
          STATUS="${{ steps.tests.outputs.status == '0' && 'passed' || 'failed' }}"
          curl -X POST https://api.treeship.dev/v1/attest \
            -H "Authorization: Bearer $TREESHIP_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
              "agent_slug": "test-runner",
              "action": "Test suite '"$STATUS"' on commit ${{ github.sha }}",
              "inputs_hash": "'$(sha256sum test-output.txt | cut -d' ' -f1)'"
            }'

Security Scan Attestation

name: Security Scan

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 0 * * *'

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run security scan
        id: scan
        run: |
          npm audit --json > audit-results.json || true
          VULNS=$(jq '.metadata.vulnerabilities.total' audit-results.json)
          echo "vulnerabilities=$VULNS" >> $GITHUB_OUTPUT
          
      - name: Attest security scan
        env:
          TREESHIP_API_KEY: ${{ secrets.TREESHIP_API_KEY }}
        run: |
          curl -X POST https://api.treeship.dev/v1/attest \
            -H "Authorization: Bearer $TREESHIP_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
              "agent_slug": "security-scanner",
              "action": "Security scan: ${{ steps.scan.outputs.vulnerabilities }} vulnerabilities found",
              "inputs_hash": "'$(sha256sum audit-results.json | cut -d' ' -f1)'",
              "metadata": {
                "scan_type": "npm_audit",
                "vulnerabilities": ${{ steps.scan.outputs.vulnerabilities }}
              }
            }'

GitLab CI

stages:
  - test
  - deploy
  - attest

test:
  stage: test
  script:
    - npm test
  artifacts:
    reports:
      junit: test-results.xml

deploy:
  stage: deploy
  script:
    - railway up
  only:
    - main

attest:
  stage: attest
  script:
    - |
      curl -X POST https://api.treeship.dev/v1/attest \
        -H "Authorization: Bearer $TREESHIP_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "agent_slug": "gitlab-ci",
          "action": "Pipeline completed: build, test, deploy",
          "inputs_hash": "'$CI_COMMIT_SHA'",
          "metadata": {
            "pipeline_id": "'$CI_PIPELINE_ID'",
            "job_id": "'$CI_JOB_ID'",
            "branch": "'$CI_COMMIT_REF_NAME'"
          }
        }'
  only:
    - main

Using the CLI

Install the Treeship CLI for simpler attestations:
npm install -g @treeship/cli
Then in your pipeline:
- name: Attest deployment
  env:
    TREESHIP_API_KEY: ${{ secrets.TREESHIP_API_KEY }}
    TREESHIP_AGENT: github-actions
  run: |
    treeship attest \
      --action "Deployed ${{ github.sha }} to production" \
      --inputs-hash "${{ github.sha }}"

Python SDK

For complex pipelines, use the Python SDK:
# ci_attest.py
import os
from treeship_sdk import Treeship

ts = Treeship(api_key=os.environ['TREESHIP_API_KEY'])

# Create attestation with full metadata
result = ts.attest(
    agent="ci-pipeline",
    action=f"Pipeline completed: {os.environ['CI_JOB_NAME']}",
    inputs_hash=ts.hash({
        "commit": os.environ['CI_COMMIT_SHA'],
        "branch": os.environ['CI_COMMIT_REF_NAME'],
        "pipeline": os.environ['CI_PIPELINE_ID'],
    }),
    metadata={
        "environment": "production",
        "triggered_by": os.environ.get('CI_COMMIT_AUTHOR', 'unknown'),
    }
)

print(f"Attestation: {result.url}")

Verification in Pipelines

Verify previous attestations before deploying:
- name: Verify staging deployment
  run: |
    # Get the latest staging attestation
    ATTESTATION=$(curl -s "https://api.treeship.dev/v1/agent/staging-deploy" | jq -r '.attestations[0].id')
    
    # Verify it
    VALID=$(curl -s "https://api.treeship.dev/v1/verify/$ATTESTATION" | jq -r '.valid')
    
    if [ "$VALID" != "true" ]; then
      echo "Staging deployment not verified!"
      exit 1
    fi
    
    echo "Staging verified, proceeding to production..."

Reusable Workflow

Create a reusable attestation workflow: .github/workflows/attest.yml:
name: Attest

on:
  workflow_call:
    inputs:
      agent:
        required: true
        type: string
      action:
        required: true
        type: string
    secrets:
      TREESHIP_API_KEY:
        required: true

jobs:
  attest:
    runs-on: ubuntu-latest
    steps:
      - name: Create attestation
        run: |
          curl -X POST https://api.treeship.dev/v1/attest \
            -H "Authorization: Bearer ${{ secrets.TREESHIP_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "agent_slug": "${{ inputs.agent }}",
              "action": "${{ inputs.action }}",
              "inputs_hash": "${{ github.sha }}"
            }'
Use in other workflows:
jobs:
  deploy:
    # ... deployment steps ...
    
  attest:
    needs: deploy
    uses: ./.github/workflows/attest.yml
    with:
      agent: "production-deploy"
      action: "Deployed v${{ github.ref_name }} to production"
    secrets:
      TREESHIP_API_KEY: ${{ secrets.TREESHIP_API_KEY }}

Best Practices

1. Unique Agent per Stage

Use different agent slugs for different pipeline stages:
  • test-runner - for test jobs
  • security-scanner - for security scans
  • staging-deploy - for staging deployments
  • production-deploy - for production deployments

2. Include Commit Hash

Always include the git commit in your attestation:
{
  "inputs_hash": "${{ github.sha }}",
  "metadata": { "commit": "${{ github.sha }}" }
}

3. Attest Failures Too

Record failed runs for complete audit trail:
- name: Attest result
  if: always()
  run: |
    STATUS="${{ job.status }}"
    treeship attest --action "Build $STATUS: ${{ github.sha }}"

4. Secure Your API Key

Store TREESHIP_API_KEY as a repository secret, never in code.

Viewing CI/CD Attestations

All pipeline attestations are visible at:
https://treeship.dev/verify/[agent-name]
For example:
  • https://treeship.dev/verify/github-actions
  • https://treeship.dev/verify/production-deploy
  • https://treeship.dev/verify/security-scanner

Next Steps