Preview Environments with GitHub Actions: Automate Per-PR Deployments with Bunnyshell
GuideMarch 20, 202612 min read

Preview Environments with GitHub Actions: Automate Per-PR Deployments with Bunnyshell

Why Integrate Bunnyshell with GitHub Actions?

Bunnyshell can create preview environments entirely on its own -- no CI/CD required. You flip a toggle, and every pull request gets an isolated deployment. So why would you use GitHub Actions at all?

Because sometimes you need more control:

  • Run tests against the preview environment before marking the PR as ready
  • Execute custom scripts -- database migrations, seed data, smoke tests -- after deployment
  • Gate deployments on CI checks (linting, type checking, security scanning)
  • Post custom comments on the PR with test results, performance metrics, or screenshots
  • Coordinate multi-repo deployments where the frontend and backend live in different repositories
  • Trigger deployments conditionally -- only for PRs with specific labels or targeting specific branches

Bunnyshell gives you two integration models with GitHub Actions:

ModelCI Config RequiredBest For
Webhook-Only (Approach A)NoneTeams that want the fastest setup with zero maintenance
GitHub Action (Approach B).github/workflows/preview-env.ymlTeams that need custom CI logic around deployments

How It Works

Webhook Mode (Zero Config)

Text
1Developer opens PR
2    → GitHub sends webhook to Bunnyshell
3    → Bunnyshell creates environment from PR branch
4    → Bunnyshell posts comment on PR with preview URL
5    → Developer pushes update
6    → Bunnyshell redeploys automatically
7    → PR merged/closed
8    → Bunnyshell destroys environment

CI-Triggered Mode (GitHub Actions)

Text
1Developer opens PR
2    → GitHub Actions workflow triggers
3    → CI runs linting, tests, type checks
4    → CI calls Bunnyshell API to create/deploy environment
5    → CI waits for deployment to complete
6    → CI runs integration tests against preview URL
7    → CI posts comment on PR with URL + test results
8    → PR merged/closed
9    → GitHub Actions workflow triggers cleanup
10    → CI calls Bunnyshell API to destroy environment

Prerequisites

Before setting up the integration, you need:

  • A Bunnyshell account with a connected Kubernetes cluster
  • A GitHub repository with your application code
  • A working bunnyshell.yaml configuration in your repo (see our framework-specific guides)
  • A primary environment deployed in Bunnyshell (the "template" for preview environments)

For Approach B (GitHub Actions), you also need:

  • A Bunnyshell API token (generate in Settings > API Tokens in the Bunnyshell dashboard)
  • Your Bunnyshell Organization ID and Project ID
  • The primary Environment ID (the one that preview environments are cloned from)

Approach A: Webhook-Only (Zero CI Config)

This is the simplest approach and works for most teams. Bunnyshell handles everything -- creating environments, deploying, posting PR comments, and cleaning up. You configure nothing in GitHub Actions.

Step 1: Deploy a Primary Environment

If you have not already, create and deploy a primary environment in Bunnyshell:

  1. Log into Bunnyshell
  2. Create a Project and Environment
  3. Paste your bunnyshell.yaml configuration
  4. Click Deploy

Step 2: Enable Automatic Preview Environments

  1. In your environment, go to Settings
  2. Find the Ephemeral environments section
  3. Toggle "Create ephemeral environments on pull request" to ON
  4. Toggle "Destroy environment after merge or close pull request" to ON
  5. Select the Kubernetes cluster for ephemeral environments

That is it. Bunnyshell automatically installs a webhook on your GitHub repository. From now on:

  • Open a PR -- Bunnyshell creates an ephemeral environment using the PR branch
  • Push to PR -- The environment redeploys with the latest changes
  • Bunnyshell comments on the PR with a link to the live deployment
  • Merge or close -- The ephemeral environment is destroyed

The webhook approach requires zero GitHub Actions configuration, zero YAML files, zero secrets management. Bunnyshell manages the entire lifecycle. This is the recommended starting point for all teams.

Step 3: Verify the Webhook

After enabling, check that the webhook was installed:

  1. Go to your GitHub repository
  2. Navigate to Settings > Webhooks
  3. You should see a webhook pointing to https://api.bunnyshell.com/...

If the webhook is missing, disconnect and reconnect the Git integration in Bunnyshell settings.

When to Upgrade to Approach B

Stick with webhook-only unless you need:

  • Custom CI checks before deployment (linting, tests, security scans)
  • Post-deployment scripts (migrations, seed data, integration tests)
  • Conditional deployments (only for labeled PRs, specific target branches)
  • Custom PR comments with test results or screenshots
  • Multi-repo coordination

Approach B: GitHub Action for Full Control

For teams that need CI logic around their preview environments. You write a GitHub Actions workflow that calls the Bunnyshell API to create, deploy, and destroy environments.

Step 1: Add Secrets to GitHub

Go to your GitHub repository Settings > Secrets and variables > Actions and add:

SecretValueDescription
BUNNYSHELL_TOKENbns_...Your Bunnyshell API token
BUNNYSHELL_ORGANIZATIONorg-abc123Your organization ID (from Bunnyshell dashboard URL)

Never hardcode the Bunnyshell API token in your workflow file. Always use GitHub Secrets. The token has full access to your Bunnyshell account and can create, modify, and destroy environments.

Step 2: Create the Deployment Workflow

Create .github/workflows/preview-env.yml:

YAML
1name: Preview Environment
2
3on:
4  pull_request:
5    types: [opened, synchronize, reopened]
6
7permissions:
8  pull-requests: write
9  contents: read
10
11env:
12  BUNNYSHELL_TOKEN: ${{ secrets.BUNNYSHELL_TOKEN }}
13  BUNNYSHELL_ORGANIZATION: ${{ secrets.BUNNYSHELL_ORGANIZATION }}
14  # The primary environment ID to clone from
15  PRIMARY_ENV_ID: "env-abc123"
16  # Project ID
17  PROJECT_ID: "proj-abc123"
18
19jobs:
20  deploy-preview:
21    name: Deploy Preview Environment
22    runs-on: ubuntu-latest
23    outputs:
24      env_id: ${{ steps.create-env.outputs.env_id }}
25      preview_url: ${{ steps.get-url.outputs.preview_url }}
26
27    steps:
28      - name: Checkout code
29        uses: actions/checkout@v4
30
31      - name: Install Bunnyshell CLI
32        run: |
33          curl -fsSL https://raw.githubusercontent.com/bunnyshell/cli/main/install.sh | sh
34          echo "$HOME/.bunnyshell/bin" >> $GITHUB_PATH
35
36      - name: Check for existing environment
37        id: check-env
38        run: |
39          ENV_NAME="pr-${{ github.event.pull_request.number }}"
40          EXISTING=$(bns environments list \
41            --organization "$BUNNYSHELL_ORGANIZATION" \
42            --project "$PROJECT_ID" \
43            --search "$ENV_NAME" \
44            --output json | jq -r '._embedded.item[0].id // empty')
45
46          if [ -n "$EXISTING" ]; then
47            echo "env_id=$EXISTING" >> $GITHUB_OUTPUT
48            echo "exists=true" >> $GITHUB_OUTPUT
49            echo "Found existing environment: $EXISTING"
50          else
51            echo "exists=false" >> $GITHUB_OUTPUT
52            echo "No existing environment found"
53          fi
54
55      - name: Create environment
56        id: create-env
57        if: steps.check-env.outputs.exists != 'true'
58        run: |
59          ENV_NAME="pr-${{ github.event.pull_request.number }}"
60          ENV_ID=$(bns environments create \
61            --name "$ENV_NAME" \
62            --from-environment "$PRIMARY_ENV_ID" \
63            --project "$PROJECT_ID" \
64            --output json | jq -r '.id')
65
66          echo "env_id=$ENV_ID" >> $GITHUB_OUTPUT
67          echo "Created environment: $ENV_ID"
68
69      - name: Set environment ID
70        id: set-env-id
71        run: |
72          if [ "${{ steps.check-env.outputs.exists }}" == "true" ]; then
73            echo "env_id=${{ steps.check-env.outputs.env_id }}" >> $GITHUB_OUTPUT
74          else
75            echo "env_id=${{ steps.create-env.outputs.env_id }}" >> $GITHUB_OUTPUT
76          fi
77
78      - name: Update environment branch
79        run: |
80          bns environments update-configuration \
81            --id "${{ steps.set-env-id.outputs.env_id }}" \
82            --git-branch "${{ github.head_ref }}"
83
84      - name: Deploy environment
85        run: |
86          bns environments deploy \
87            --id "${{ steps.set-env-id.outputs.env_id }}" \
88            --wait \
89            --timeout 600
90
91      - name: Get preview URL
92        id: get-url
93        run: |
94          PREVIEW_URL=$(bns environments show \
95            --id "${{ steps.set-env-id.outputs.env_id }}" \
96            --output json | jq -r '._embedded.components[0].endpoints[0] // "pending"')
97
98          echo "preview_url=$PREVIEW_URL" >> $GITHUB_OUTPUT
99          echo "Preview URL: $PREVIEW_URL"
100
101      - name: Comment on PR
102        uses: actions/github-script@v7
103        with:
104          script: |
105            const envId = '${{ steps.set-env-id.outputs.env_id }}';
106            const previewUrl = '${{ steps.get-url.outputs.preview_url }}';
107            const body = [
108              '## Preview Environment Ready',
109              '',
110              `| | |`,
111              `|---|---|`,
112              `| **Preview URL** | ${previewUrl} |`,
113              `| **Environment ID** | \`${envId}\` |`,
114              `| **Status** | Deployed |`,
115              `| **Branch** | \`${{ github.head_ref }}\` |`,
116              '',
117              '> Deployed by [Bunnyshell](https://www.bunnyshell.com)',
118            ].join('\n');
119
120            // Find existing comment to update
121            const { data: comments } = await github.rest.issues.listComments({
122              owner: context.repo.owner,
123              repo: context.repo.repo,
124              issue_number: context.issue.number,
125            });
126
127            const botComment = comments.find(c =>
128              c.body.includes('## Preview Environment Ready')
129            );
130
131            if (botComment) {
132              await github.rest.issues.updateComment({
133                owner: context.repo.owner,
134                repo: context.repo.repo,
135                comment_id: botComment.id,
136                body,
137              });
138            } else {
139              await github.rest.issues.createComment({
140                owner: context.repo.owner,
141                repo: context.repo.repo,
142                issue_number: context.issue.number,
143                body,
144              });
145            }

The workflow checks for an existing environment before creating a new one. This handles the synchronize event (new commits pushed to the PR) -- instead of creating a new environment for every push, it redeploys the existing one.

Step 3: Create the Cleanup Workflow

Create .github/workflows/preview-env-cleanup.yml:

YAML
1name: Preview Environment Cleanup
2
3on:
4  pull_request:
5    types: [closed]
6
7env:
8  BUNNYSHELL_TOKEN: ${{ secrets.BUNNYSHELL_TOKEN }}
9  BUNNYSHELL_ORGANIZATION: ${{ secrets.BUNNYSHELL_ORGANIZATION }}
10  PROJECT_ID: "proj-abc123"
11
12jobs:
13  cleanup:
14    name: Destroy Preview Environment
15    runs-on: ubuntu-latest
16
17    steps:
18      - name: Install Bunnyshell CLI
19        run: |
20          curl -fsSL https://raw.githubusercontent.com/bunnyshell/cli/main/install.sh | sh
21          echo "$HOME/.bunnyshell/bin" >> $GITHUB_PATH
22
23      - name: Find and destroy environment
24        run: |
25          ENV_NAME="pr-${{ github.event.pull_request.number }}"
26          ENV_ID=$(bns environments list \
27            --organization "$BUNNYSHELL_ORGANIZATION" \
28            --project "$PROJECT_ID" \
29            --search "$ENV_NAME" \
30            --output json | jq -r '._embedded.item[0].id // empty')
31
32          if [ -n "$ENV_ID" ]; then
33            echo "Destroying environment: $ENV_ID"
34            bns environments delete \
35              --id "$ENV_ID" \
36              --wait \
37              --timeout 300
38            echo "Environment destroyed successfully"
39          else
40            echo "No environment found for $ENV_NAME"
41          fi
42
43      - name: Comment on PR
44        if: success()
45        uses: actions/github-script@v7
46        with:
47          script: |
48            const { data: comments } = await github.rest.issues.listComments({
49              owner: context.repo.owner,
50              repo: context.repo.repo,
51              issue_number: context.issue.number,
52            });
53
54            const botComment = comments.find(c =>
55              c.body.includes('## Preview Environment Ready')
56            );
57
58            if (botComment) {
59              const merged = context.payload.pull_request.merged;
60              const action = merged ? 'merged' : 'closed';
61              await github.rest.issues.updateComment({
62                owner: context.repo.owner,
63                repo: context.repo.repo,
64                comment_id: botComment.id,
65                body: [
66                  '## Preview Environment Destroyed',
67                  '',
68                  `The preview environment was automatically destroyed because the PR was ${action}.`,
69                  '',
70                  '> Managed by [Bunnyshell](https://www.bunnyshell.com)',
71                ].join('\n'),
72              });
73            }

The Bunnyshell GitHub Action

For a more streamlined integration, Bunnyshell provides an official GitHub Action:

YAML
1- name: Deploy to Bunnyshell
2  uses: bunnyshell/deploy-action@v2
3  with:
4    bunnyshell-token: ${{ secrets.BUNNYSHELL_TOKEN }}
5    bunnyshell-organization: ${{ secrets.BUNNYSHELL_ORGANIZATION }}
6    environment-id: ${{ env.ENV_ID }}
7    wait: true
8    timeout: 600

The action wraps the CLI and handles:

  • Authentication
  • Deployment with wait and timeout
  • Error handling and retries
  • Output variables (environment URL, status)

The official GitHub Action is a convenience wrapper. Under the hood, it calls the same bns environments deploy CLI command. If you need more control (custom scripts, conditional logic), use the CLI directly as shown in Approach B.


Workflow Examples

Deploy on PR Open, Destroy on Close

This is the most common pattern. The examples in Approach B above implement this fully. Here is a minimal version:

YAML
1# .github/workflows/preview-env-minimal.yml
2name: Preview Environment (Minimal)
3
4on:
5  pull_request:
6    types: [opened, synchronize, closed]
7
8env:
9  BUNNYSHELL_TOKEN: ${{ secrets.BUNNYSHELL_TOKEN }}
10
11jobs:
12  deploy:
13    if: github.event.action != 'closed'
14    runs-on: ubuntu-latest
15    steps:
16      - name: Install CLI
17        run: |
18          curl -fsSL https://raw.githubusercontent.com/bunnyshell/cli/main/install.sh | sh
19          echo "$HOME/.bunnyshell/bin" >> $GITHUB_PATH
20
21      - name: Deploy
22        run: |
23          bns environments deploy \
24            --id "env-abc123" \
25            --wait
26
27  destroy:
28    if: github.event.action == 'closed'
29    runs-on: ubuntu-latest
30    steps:
31      - name: Install CLI
32        run: |
33          curl -fsSL https://raw.githubusercontent.com/bunnyshell/cli/main/install.sh | sh
34          echo "$HOME/.bunnyshell/bin" >> $GITHUB_PATH
35
36      - name: Destroy
37        run: |
38          bns environments delete \
39            --id "env-abc123" \
40            --wait

Deploy Only for Labeled PRs

Only create preview environments for PRs with the preview label:

YAML
1name: Preview Environment (Labeled)
2
3on:
4  pull_request:
5    types: [opened, synchronize, labeled]
6
7jobs:
8  deploy:
9    if: contains(github.event.pull_request.labels.*.name, 'preview')
10    runs-on: ubuntu-latest
11    steps:
12      - name: Install CLI
13        run: |
14          curl -fsSL https://raw.githubusercontent.com/bunnyshell/cli/main/install.sh | sh
15          echo "$HOME/.bunnyshell/bin" >> $GITHUB_PATH
16
17      - name: Deploy preview
18        run: |
19          bns environments deploy \
20            --id "env-abc123" \
21            --wait

Comment with Preview URL

A reusable pattern for posting the preview URL as a PR comment:

YAML
1      - name: Get endpoints
2        id: endpoints
3        run: |
4          ENDPOINTS=$(bns environments show \
5            --id "$ENV_ID" \
6            --output json | jq -r '[._embedded.components[] | .endpoints[]?] | join("\n")')
7          echo "endpoints<<EOF" >> $GITHUB_OUTPUT
8          echo "$ENDPOINTS" >> $GITHUB_OUTPUT
9          echo "EOF" >> $GITHUB_OUTPUT
10
11      - name: Comment preview URL
12        uses: actions/github-script@v7
13        with:
14          script: |
15            const endpoints = `${{ steps.endpoints.outputs.endpoints }}`;
16            const lines = endpoints.split('\n').filter(Boolean);
17            const urlList = lines.map(url => `- ${url}`).join('\n');
18
19            await github.rest.issues.createComment({
20              owner: context.repo.owner,
21              repo: context.repo.repo,
22              issue_number: context.issue.number,
23              body: `## Preview Environment\n\n${urlList}\n\n> Deployed by Bunnyshell`,
24            });

Environment Variables and Secrets

GitHub Secrets to Bunnyshell

Pass secrets from GitHub to your Bunnyshell environment:

YAML
1      - name: Update environment variables
2        run: |
3          bns environments update \
4            --id "$ENV_ID" \
5            --var "API_KEY=${{ secrets.API_KEY }}" \
6            --var "DATABASE_URL=${{ secrets.DATABASE_URL }}" \
7            --var "STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }}"

Dynamic Variables per PR

Set PR-specific variables:

YAML
1      - name: Set PR variables
2        run: |
3          bns environments update \
4            --id "$ENV_ID" \
5            --var "APP_ENV=preview" \
6            --var "PR_NUMBER=${{ github.event.pull_request.number }}" \
7            --var "PR_AUTHOR=${{ github.event.pull_request.user.login }}" \
8            --var "COMMIT_SHA=${{ github.sha }}"

Bunnyshell Secrets

For sensitive values, use Bunnyshell's SECRET["..."] syntax in your bunnyshell.yaml. These are encrypted at rest and never exposed in logs:

YAML
1environmentVariables:
2  DB_PASSWORD: SECRET["your-database-password"]
3  API_SECRET: SECRET["your-api-secret"]

Bunnyshell secrets defined in bunnyshell.yaml take precedence over variables set via the CLI. Use GitHub Secrets for values that change per PR. Use Bunnyshell secrets for values that are the same across all preview environments.


Running Tests Against Preview Environments

One of the biggest advantages of CI-triggered preview environments is running tests against them:

Smoke Tests After Deployment

YAML
1      - name: Wait for healthy deployment
2        run: |
3          PREVIEW_URL=$(bns environments show \
4            --id "$ENV_ID" \
5            --output json | jq -r '._embedded.components[0].endpoints[0]')
6
7          echo "Waiting for $PREVIEW_URL to respond..."
8          for i in $(seq 1 30); do
9            STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$PREVIEW_URL/health" || echo "000")
10            if [ "$STATUS" = "200" ]; then
11              echo "Application is healthy!"
12              exit 0
13            fi
14            echo "Attempt $i: status $STATUS, retrying in 10s..."
15            sleep 10
16          done
17
18          echo "Application did not become healthy in 5 minutes"
19          exit 1
20
21      - name: Run smoke tests
22        run: |
23          PREVIEW_URL=$(bns environments show \
24            --id "$ENV_ID" \
25            --output json | jq -r '._embedded.components[0].endpoints[0]')
26
27          # Basic API tests
28          curl -f "$PREVIEW_URL/api/health" || exit 1
29          curl -f "$PREVIEW_URL/api/version" || exit 1
30
31          echo "Smoke tests passed!"

Cypress E2E Tests

YAML
1      - name: Run Cypress tests
2        uses: cypress-io/github-action@v6
3        with:
4          config: baseUrl=${{ steps.get-url.outputs.preview_url }}
5          record: true
6        env:
7          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

Playwright Tests

YAML
1      - name: Run Playwright tests
2        run: |
3          npx playwright install --with-deps chromium
4          PREVIEW_URL="${{ steps.get-url.outputs.preview_url }}" \
5            npx playwright test --reporter=github

Running E2E tests against preview environments gives you confidence that the PR works in a production-like setting. Failed tests block the merge, catching regressions before they reach production.


Advanced: Matrix Builds and Multiple Environments

Multiple Services in Different Repos

If your frontend and backend live in separate repositories, coordinate their preview environments:

YAML
1# In the frontend repo
2name: Frontend Preview
3
4on:
5  pull_request:
6    types: [opened, synchronize]
7
8jobs:
9  deploy:
10    runs-on: ubuntu-latest
11    steps:
12      - name: Install CLI
13        run: |
14          curl -fsSL https://raw.githubusercontent.com/bunnyshell/cli/main/install.sh | sh
15          echo "$HOME/.bunnyshell/bin" >> $GITHUB_PATH
16
17      - name: Deploy frontend with backend
18        run: |
19          ENV_NAME="frontend-pr-${{ github.event.pull_request.number }}"
20
21          # Create environment from template that includes both services
22          ENV_ID=$(bns environments create \
23            --name "$ENV_NAME" \
24            --from-environment "$PRIMARY_ENV_ID" \
25            --project "$PROJECT_ID" \
26            --output json | jq -r '.id')
27
28          # Update only the frontend component's branch
29          bns environments update-configuration \
30            --id "$ENV_ID" \
31            --component frontend \
32            --git-branch "${{ github.head_ref }}"
33
34          # Deploy
35          bns environments deploy --id "$ENV_ID" --wait

Matrix: Test Against Multiple Configurations

YAML
1name: Preview Matrix
2
3on:
4  pull_request:
5    types: [opened]
6
7jobs:
8  deploy:
9    strategy:
10      matrix:
11        config:
12          - name: "postgres"
13            env_template: "env-postgres-123"
14          - name: "mysql"
15            env_template: "env-mysql-456"
16    runs-on: ubuntu-latest
17    steps:
18      - name: Install CLI
19        run: |
20          curl -fsSL https://raw.githubusercontent.com/bunnyshell/cli/main/install.sh | sh
21          echo "$HOME/.bunnyshell/bin" >> $GITHUB_PATH
22
23      - name: Deploy ${{ matrix.config.name }} variant
24        run: |
25          ENV_NAME="pr-${{ github.event.pull_request.number }}-${{ matrix.config.name }}"
26          bns environments create \
27            --name "$ENV_NAME" \
28            --from-environment "${{ matrix.config.env_template }}" \
29            --project "$PROJECT_ID"
30
31          bns environments deploy --id "$ENV_ID" --wait

Matrix builds multiply your Kubernetes resource usage. If you have 10 open PRs and 3 matrix configurations, that is 30 preview environments. Monitor your cluster capacity and set concurrency limits on the workflow.


Troubleshooting

IssueSolution
"401 Unauthorized" from Bunnyshell CLIThe BUNNYSHELL_TOKEN secret is missing or expired. Regenerate the token in Bunnyshell dashboard and update the GitHub Secret.
Workflow fails with "environment not found"The PRIMARY_ENV_ID or PROJECT_ID is incorrect. Copy the exact IDs from the Bunnyshell dashboard URL.
Deployment times outIncrease the --timeout value. Default is 300 seconds. Large images or slow clusters may need 600-900 seconds.
PR comment not appearingThe workflow needs pull-requests: write permission. Add it to the permissions block.
Environment not destroyed on PR closeThe cleanup workflow only triggers on pull_request: closed. Make sure the workflow file exists and the BUNNYSHELL_TOKEN secret is set.
Duplicate environments createdThe "check for existing environment" step may be failing silently. Verify the jq query matches the environment name pattern you are using.
"Resource quota exceeded"Your Kubernetes cluster has resource limits. Destroy old preview environments or increase cluster capacity.
Workflow runs on forksBy default, pull_request events from forks cannot access secrets. Use pull_request_target (with caution) or require forks to use the webhook approach.
Slow CLI installationCache the Bunnyshell CLI binary using actions/cache@v4 with the CLI version as the cache key.
Webhook and Actions conflictIf you have both webhook mode enabled and GitHub Actions deploying, you will get duplicate environments. Disable webhook mode when using Actions.

What's Next?

  • Add status checks -- Mark the PR as "pending" while the preview environment deploys, and "success" when it is ready
  • Add Slack notifications -- Post preview URLs to a Slack channel using the Slack GitHub Action
  • Add visual regression testing -- Use Percy or Chromatic to catch UI changes in preview environments
  • Add performance budgets -- Run Lighthouse CI against the preview URL and fail the PR if scores drop
  • Build a deployment dashboard -- Use the Bunnyshell API to build a custom dashboard showing all active preview environments

Ship faster starting today.

14-day full-feature trial. No credit card required. Pay-as-you-go from $0.007/min per environment.