PR Preview Deployments Guide
This guide explains how to set up and use PR preview deployments with Shipper.
Overview
PR preview deployments automatically create temporary environments for each pull request, allowing you to:
- Test changes before merging
- Share work-in-progress with stakeholders
- Catch integration issues early
- Review UI/UX changes in a real environment
How It Works
- A pull request is opened against
mainordevelopbranch - GitHub Actions triggers the preview deployment workflow
- Shipper creates a new site with a unique domain (e.g.,
api-preview-123.example.com) - Shipper creates dedicated preview databases
- The PR is updated with deployment status and links
- When the PR is closed or merged, the preview environment is automatically cleaned up
Configuration
1. Configure Preview Profile in shipper.yml
Add a preview profile to your projects:
projects:
api:
provider: ploi
path: ./api
repository:
provider: github
name: ulties/shipper
databases:
main:
name: "shipper_${PROJECT_NAME}_${PROFILE}_${GITHUB_PR_NUMBER}"
user: "shipper_${PROJECT_NAME}_${PROFILE}_${GITHUB_PR_NUMBER}"
type: mysql
profiles:
preview:
branch: "${GITHUB_HEAD_REF}"
domain: "api-preview-${GITHUB_PR_NUMBER}.example.com"
Key Points:
branch: Use${GITHUB_HEAD_REF}to deploy the PR branchdomain: Use${GITHUB_PR_NUMBER}to create unique domainsdatabases: Use${GITHUB_PR_NUMBER}for PR-specific databases
2. Create Preview Deployment Workflow
Create .github/workflows/deploy-preview.yml:
name: Deploy Preview
on:
pull_request:
branches:
- main
- develop
permissions:
contents: read
pull-requests: write
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
project: [api, frontend]
name: Deploy ${{ matrix.project }} Preview
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, xml, ctype, json, yaml
coverage: none
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-dev
- name: Validate configuration
run: ./shipper validate
env:
PLOI_API_KEY: ${{ secrets.PLOI_API_KEY }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
- name: Plan deployment
run: ./shipper plan ${{ matrix.project }} --profile=preview
env:
PLOI_API_KEY: ${{ secrets.PLOI_API_KEY }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
- name: Deploy preview
run: ./shipper apply ${{ matrix.project }} --profile=preview --force
env:
PLOI_API_KEY: ${{ secrets.PLOI_API_KEY }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const domain = '${{ matrix.project }}' === 'api'
? 'api-preview-${{ github.event.pull_request.number }}.example.com'
: 'preview-${{ github.event.pull_request.number }}.example.com';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `๐ Preview deployed for **${{ matrix.project }}**!\n\n**URL:** https://${domain}\n**Branch:** \`${{ github.head_ref }}\`\n**Profile:** preview`
})
3. Create Cleanup Workflow
Create .github/workflows/cleanup-preview.yml:
name: Cleanup Preview
on:
pull_request:
types: [closed]
jobs:
cleanup:
runs-on: ubuntu-latest
strategy:
matrix:
project: [api, frontend]
name: Cleanup ${{ matrix.project }} Preview
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, xml, ctype, json, yaml
coverage: none
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-dev
- name: Destroy preview
run: ./shipper destroy ${{ matrix.project }} --profile=preview --force
env:
PLOI_API_KEY: ${{ secrets.PLOI_API_KEY }}
GITHUB_HEAD_REF: ${{ github.event.pull_request.head.ref }}
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `๐งน Preview environment for **${{ matrix.project }}** has been cleaned up.`
})
Using the GitHub Action
Alternatively, use the Shipper GitHub Action for simpler configuration:
name: Deploy Preview
on:
pull_request:
branches: [main, develop]
permissions:
contents: read
pull-requests: write
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
project: [api, frontend]
steps:
- uses: actions/checkout@v4
- name: Deploy Preview
uses: ulties/shipper/.github/actions/shipper@main
with:
command: apply
project: ${{ matrix.project }}
profile: preview
force: true
env:
PLOI_API_KEY: ${{ secrets.PLOI_API_KEY }}
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
Environment Variables
Required environment variables for PR previews:
PLOI_API_KEY: Your Ploi API key (from GitHub secrets)GITHUB_HEAD_REF: PR branch name (automatically available)GITHUB_PR_NUMBER: PR number (automatically available)
Set the Ploi API key in your repository secrets:
- Go to Settings โ Secrets and variables โ Actions
- Click "New repository secret"
- Name:
PLOI_API_KEY - Value: Your Ploi API key
Preview Domain Strategy
Subdomain Pattern
Use a consistent pattern for preview domains:
domain: "api-preview-${GITHUB_PR_NUMBER}.example.com"
This creates domains like:
api-preview-123.example.comfor PR #123api-preview-456.example.comfor PR #456
Wildcard DNS
Configure a wildcard DNS record for your preview domains:
DNS Record:
Type: A or CNAME
Name: *.example.com
Value: [Your server IP or hostname]
This allows any subdomain to automatically resolve to your server.
SSL Certificates
Ploi automatically provisions SSL certificates via Let's Encrypt for preview domains.
Database Management
Automatic Creation
Preview databases are automatically created with unique names:
databases:
main:
name: "shipper_${PROJECT_NAME}_preview_${GITHUB_PR_NUMBER}"
user: "shipper_${PROJECT_NAME}_preview_${GITHUB_PR_NUMBER}"
type: mysql
Automatic Cleanup
When the cleanup workflow runs:
- The preview site is destroyed
- Associated databases are automatically deleted
- Database users are removed
Data Seeding
To seed preview databases with test data, add a deploy script:
In your Laravel project:
# .ploi/deploy.sh
php artisan migrate --force
php artisan db:seed --force
Commenting on Pull Requests
The workflow automatically comments on PRs with deployment status:
Success Comment:
๐ Preview deployed for **api**!
**URL:** https://api-preview-123.example.com
**Branch:** `feature/new-feature`
**Profile:** preview
Cleanup Comment:
๐งน Preview environment for **api** has been cleaned up.
Custom Comments
Customize the PR comment in the workflow:
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const domain = 'api-preview-${{ github.event.pull_request.number }}.example.com';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## ๐ Preview Deployment
**URL:** https://${domain}
**Branch:** \`${{ github.head_ref }}\`
**Commit:** ${{ github.sha }}
### Test Credentials
- **Email:** test@example.com
- **Password:** password
This preview will be automatically cleaned up when the PR is closed.`
})
Automatic Cleanup Strategies
On PR Close (Recommended)
Clean up when PR is closed or merged:
on:
pull_request:
types: [closed]
Weekly Cleanup
Create a scheduled cleanup for stale previews:
name: Weekly Cleanup
on:
schedule:
- cron: '0 0 * * 0' # Every Sunday at midnight
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Add logic to find and cleanup old preview sites
# This requires custom scripting based on your needs
Troubleshooting
Issue: Preview not deploying
Check:
- Verify
PLOI_API_KEYis set in repository secrets - Check workflow run logs in Actions tab
- Verify shipper.yml has preview profile configured
- Ensure DNS wildcard is configured
Issue: Domain not accessible
Check:
- Verify DNS wildcard record is configured
- Wait a few minutes for DNS propagation
- Check Ploi dashboard for SSL certificate status
- Verify site is deployed in Ploi
Issue: Database connection errors
Check:
- Verify database was created (check Ploi dashboard)
- Check environment variables are set correctly
- Verify database credentials in site environment variables
Issue: Cleanup not working
Check:
- Verify cleanup workflow is triggered on PR close
- Check that
GITHUB_PR_NUMBERis available in cleanup workflow - Verify Ploi API key has permissions to delete sites
Best Practices
- Use Consistent Naming: Always include PR number in domain and database names
- Automate Cleanup: Always configure automatic cleanup on PR close
- Test Before Merging: Verify preview deployment before approving PR
- Seed Test Data: Include data seeding in deploy scripts
- Share Credentials: Include test credentials in PR comments if needed
- Monitor Costs: Preview environments consume server resources
- Limit Preview Scope: Only deploy what's necessary for testing
- Use Matrix Strategy: Deploy multiple projects in parallel
Security Considerations
- API Keys: Store Ploi API key in GitHub secrets, never in code
- Preview Access: Consider adding basic auth to preview sites
- Test Data: Don't use production data in preview databases
- Cleanup: Always clean up previews to prevent resource leaks
- Permissions: Use minimal GitHub Actions permissions required
Next Steps
- Sites Management - Learn about site lifecycle management
- Database Management - Deep dive into database features
- GitHub Actions Setup - Complete GitHub Actions guide