Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Test
on: [push, pull_request]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.14'
- run: bun install --frozen-lockfile
- run: bun test
- run: bun run check
- name: Run the composite action with previews disabled
id: skip
uses: ./
- name: Verify skipped deployment
env:
DEPLOY_STATUS: ${{ steps.skip.outputs.status }}
run: test "$DEPLOY_STATUS" = skipped
- name: Build the shared Cloud and Fly example
run: docker build -t rivet-counter examples/counter
- name: Verify plain registry.start on cold boot and restart
run: python3 scripts/smoke-start.py rivet-counter
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
.vscode/
.DS_Store
node_modules/
.env*
*.tar
157 changes: 99 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,105 @@
# Rivet Deploy Action
# Rivet Deploy Action — v3

Creates Rivet namespaces for preview deployments.
Map branches to **existing Rivet Cloud clusters**. Optionally deploy every other branch to a **single Fly Machine** running the app with RivetKit's automatic local engine, like a dev machine. Unmapped branches are skipped when previews are disabled or no Fly token is configured.

## Setup

Add these GitHub Actions secrets:

- `RIVET_CLOUD_TOKEN`: a Rivet Cloud credential with cluster deployment access in your organization. An Engine admin token cannot deploy builds. The action uses the [Rivet CLI](https://rivet.dev/docs/cli/) and passes this secret through `RIVET_CLOUD_TOKEN`.
- `FLY_API_TOKEN`: a Fly organization token that can create, deploy, and delete preview apps. Only needed for Fly previews.

Use the published CLI with v3 support, for example `rivet-cli-version: '3.0.0'`.

**Known restart issue:** RivetKit `3.0.0` can mistake a reused PID from a previous VM boot for a still-running local Engine. The consumer test documents a temporary container startup workaround; the action does not apply it. Plain app startup remains `registry.start()`. A fixed RivetKit release is needed for an unmodified, restart-safe container.

Create `.github/workflows/rivet-deploy.yml`:

```yaml
name: Deploy
on:
push:
branches: [main, staging]
pull_request:
types: [opened, synchronize, reopened, closed]
workflow_dispatch:

concurrency:
group: rivet-deploy-${{ github.head_ref || (github.event_name == 'delete' && github.event.ref) || github.ref_name }}
cancel-in-progress: false

jobs:
deploy:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
# Use the v3 release tag once published; a commit SHA also works.
- uses: rivet-dev/deploy-action@v3
with:
rivet-token: ${{ secrets.RIVET_CLOUD_TOKEN }}
rivet-cli-version: '3.0.0'
rivet-org: my-org
branch-clusters: |
{
"main": "production",
"staging": { "cluster": "staging", "region": "us-east4", "pool": "default" }
}
fly-enabled: 'true'
fly-token: ${{ secrets.FLY_API_TOKEN }}
fly-org: my-fly-org
```

The mapping uses exact **source branch names**, including for pull requests. Each cluster may be mapped to only one branch. Specify a region when a cluster has more than one; otherwise Rivet selects its only region. Existing pool settings, including its allowlist, are preserved.

PR-close events delete the Fly app and its volume. They **never delete Cloud clusters**. Keep the Fly token configured for cleanup even if you later disable previews. Fork PRs and tag events are skipped. For push-only previews, also subscribe to the `delete` event to clean up deleted branches. Keep deployment and cleanup in the same concurrency group.

## Container requirements

Use RivetKit v3. The image must run your registry and serve your app's HTTP server on `0.0.0.0:$PORT` (default `8080`). `registry.start()` alone does not serve your app's HTTP endpoint. Use normal `setup({ use: … })` and `registry.start()`; no provider-specific runtime overrides are needed.

- **Cloud:** Rivet supplies the engine connection and pool configuration. Keep the image in production mode.
- **Fly:** the action sets `NODE_ENV=development` and `RIVETKIT_STORAGE_PATH=/data`. Let RivetKit start its engine normally. Keep its native and engine packages in the image; do not hardcode `startEngine: false` or bake remote Rivet credentials into it. The container user must be able to write `/data`.

The [counter example](examples/counter) uses the same code and Dockerfile on both providers, with `registry.start()` and no runtime overrides.

Only the app's HTTP port is public; the local engine stays inside the Machine. Browser clients should call your app's backend. The returned app URL is **not** `RIVET_ENDPOINT`.

Fly creates one Machine with a 20 GiB persistent volume. Increasing `fly-volume-size` grows the existing volume on redeploy; lowering it never shrinks or replaces the volume. RivetKit v3’s local engine reserves 4 GiB for its log; smaller-than-5 GiB volumes cannot start it. It stops when idle and starts on incoming HTTP requests. Actor data survives stops and image updates, but is removed on preview cleanup. Open connections can keep it awake; background work cannot wake a stopped Machine. Volume storage remains billable while stopped. See [Fly autostop](https://fly.io/docs/launch/autostop-autostart/) and [volumes](https://fly.io/docs/volumes/overview/).

## Inputs

| Input | Required | Default | Description |
|:------|:---------|:--------|:------------|
| `rivet-token` | Yes | - | Rivet Cloud API token |
| `rivet-endpoint` | No | `https://api.rivet.dev` | Rivet Engine API endpoint |
| `github-token` | No | `${{ github.token }}` | GitHub token for PR comments |
| `main-branch` | No | `main` | Main branch name for production deployments |
| `docker-build-path` | No | `.` | Docker build context directory |
| `dockerfile-path` | No | `Dockerfile` | Dockerfile location |
| `compute-pool-config` | No | `{ }` | JSON property overrides for compute pool configuration |
| `prod-namespace` | No | - | **(Advanced)** Override the production namespace slug instead of auto-detecting the first `production-*` namespace |
| Input | Default | Purpose |
| --- | --- | --- |
| `branch-clusters` | `{}` | Branch → cluster mapping |
| `rivet-token`, `rivet-org` | — | Required for mapped Cloud deployments |
| `rivet-cli-version` | `latest` | CLI release with v3 cluster support; pin for reproducibility |
| `rivet-cli-path` | — | Use a preinstalled v3 CLI instead of downloading one |
| `docker-build-path` | `.` | Build context, relative to checkout |
| `dockerfile-path` | `Dockerfile` | Dockerfile, relative to checkout |
| `fly-enabled` | `false` | Enable previews for unmapped branches |
| `fly-token`, `fly-org` | — | Required for Fly deployment and cleanup |
| `fly-region` | `iad` | Preview region; keep stable across updates |
| `fly-port` | `8080` | App HTTP port |
| `fly-memory` | `1024` | Machine memory, MiB |
| `fly-volume-size` | `20` | Data volume size, GiB (minimum 5; grows existing volumes) |
| `github-token` | `${{ github.token }}` | PR status comments |

## Setup
PR comments update in place with a status table and workflow logs. Ready deployments include an **Open preview** link. Skipped previews explain why; PR closure reports removal or retention of a mapped cluster. Cancellation reporting runs as a final action step and is best-effort if the runner is forcibly terminated.

Outputs: `status` (`ready`, `skipped`, `closed`), `provider` (`rivet`, `fly`), `url`, `build-id` (Cloud), and `fly-app` (Fly).

## Upgrading from v1

v3 uses branch-to-cluster mappings instead of creating namespaces. Replace `main-branch`, `prod-namespace`, `rivet-endpoint`, `runner-config`, and `compute-pool-config` with the mapping above. Configure cluster resources in Rivet. Keep using the v1 tag for the old namespace-based deployment flow.

## Development

1. Get your Rivet token from [Rivet Dashboard](https://dashboard.rivet.dev) > Settings > Advanced > Manual Client Configuration

2. Add secret to your repository:
```bash
gh secret set RIVET_CLOUD_TOKEN
```

3. Create `.github/workflows/rivet-deploy.yml`:
```yaml
name: Rivet Deploy

on:
pull_request:
types: [opened, synchronize, reopened, closed]
push:
branches: [main]
workflow_dispatch:

concurrency:
group: rivet-deploy-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
rivet-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: rivet-dev/deploy-action@v1.1.2
with:
rivet-token: ${{ secrets.RIVET_CLOUD_TOKEN }}
```

## How It Works

When a PR is opened or updated:

1. The action creates a Rivet namespace for the PR (or reuses an existing one)
2. The Dockerfile is built
3. The image is uploaded to Rivet's docker registry
4. A runner pool using the image is created in the new namespace

When a PR is closed, the action archives the corresponding Rivet namespace to keep your project tidy.
```sh
bun install --frozen-lockfile
bun test
bun run check
```
146 changes: 97 additions & 49 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,67 +1,115 @@
name: 'Rivet Deploy'
description: 'Build and deploy Docker images to Rivet Compute'
author: 'Rivet'
name: Rivet Deploy
description: Deploy mapped branches to Rivet v3 clusters and optional Fly.io previews
author: Rivet
branding:
icon: 'server'
color: 'orange'

icon: server
color: orange
inputs:
branch-clusters:
description: 'JSON map of exact branch names to cluster names or {cluster, region, pool} objects'
default: '{}'
rivet-token:
description: 'Rivet Cloud API token'
required: true
rivet-endpoint:
description: 'Rivet Engine API endpoint'
required: false
default: 'https://api.rivet.dev'
description: 'Rivet Cloud credential with cluster deployment access (not an Engine admin token)'
rivet-org:
description: 'Organization slug for mapped Rivet Cloud clusters'
rivet-cli-version:
description: '@rivetkit/cli version or dist-tag with v3 --cluster support'
default: latest
rivet-cli-path:
description: Optional preinstalled v3 CLI executable; overrides rivet-cli-version
github-token:
description: 'GitHub token for PR comments'
required: false
description: Token for PR status comments
default: ${{ github.token }}
main-branch:
description: 'Main branch name for production deployments'
required: false
default: 'main'
docker-build-path:
description: 'Docker build context directory'
required: false
description: Docker build context, relative to the checkout
default: '.'
dockerfile-path:
description: 'Dockerfile location'
required: false
default: 'Dockerfile'
runner-config:
description: 'JSON property overrides for runner configuration'
required: false
default: '{}'
compute-pool-config:
description: 'JSON property overrides for compute pool configuration'
required: false
default: '{}'
prod-namespace:
description: '(Advanced) Override the production namespace slug instead of auto-detecting the first prod-* namespace'
required: false
default: ''

description: Dockerfile, relative to the checkout
default: Dockerfile
fly-enabled:
description: Deploy unmapped branches to Fly.io
default: 'false'
fly-token:
description: Fly.io token with permission to create and delete apps in fly-org
fly-org:
description: Fly.io organization slug
fly-region:
description: Fly preview region; keep unchanged while a preview exists
default: iad
fly-port:
description: HTTP port served by the app in the container
default: '8080'
fly-memory:
description: Memory in MiB for the single preview Machine
default: '1024'
fly-volume-size:
description: Size in GiB of the local RivetKit data volume; existing volumes grow when increased
default: '20'
outputs:
status:
description: ready, skipped, or closed
value: ${{ steps.deploy.outputs.status }}
provider:
description: rivet or fly
value: ${{ steps.deploy.outputs.provider }}
url:
description: Public app URL (not the Rivet Engine API)
value: ${{ steps.deploy.outputs.url }}
build-id:
description: Rivet build ID for rollback
value: ${{ steps.deploy.outputs.build-id }}
fly-app:
description: Fly preview app name
value: ${{ steps.deploy.outputs.fly-app }}
runs:
using: 'composite'
using: composite
steps:
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Deploy to Rivet
bun-version: '1.3.14'
- uses: superfly/flyctl-actions/setup-flyctl@98172f70657cb3e15db199af139be99bd3e61200
if: inputs.fly-enabled == 'true' || inputs.fly-token != ''
with:
version: '0.4.110'
- name: Install action dependencies
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
run: bun install --frozen-lockfile --cwd "$ACTION_PATH"
- name: Deploy
id: deploy
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
GITHUB_TOKEN: ${{ inputs.github-token }}
BRANCH_CLUSTERS: ${{ inputs.branch-clusters }}
RIVET_CLOUD_TOKEN: ${{ inputs.rivet-token }}
RIVET_ENGINE_ENDPOINT: ${{ inputs.rivet-endpoint }}
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
REPO_FULL_NAME: ${{ github.repository }}
RUN_ID: ${{ github.run_id }}
MAIN_BRANCH: ${{ inputs.main-branch }}
RIVET_CLI_VERSION: ${{ inputs.rivet-cli-version }}
RIVET_CLI_PATH: ${{ inputs.rivet-cli-path }}
RIVET_ORG: ${{ inputs.rivet-org }}
DOCKER_BUILD_PATH: ${{ inputs.docker-build-path }}
DOCKERFILE_PATH: ${{ inputs.dockerfile-path }}
COMPUTE_POOL_CONFIG: ${{ inputs.compute-pool-config }}
PROD_NAMESPACE_OVERRIDE: ${{ inputs.prod-namespace }}
run: bun run ${{ github.action_path }}/src/index.ts
FLY_ENABLED: ${{ inputs.fly-enabled }}
FLY_API_TOKEN: ${{ inputs.fly-token }}
FLY_ORG: ${{ inputs.fly-org }}
FLY_REGION: ${{ inputs.fly-region }}
FLY_PORT: ${{ inputs.fly-port }}
FLY_MEMORY: ${{ inputs.fly-memory }}
FLY_VOLUME_SIZE: ${{ inputs.fly-volume-size }}
run: bun run "$ACTION_PATH/src/index.ts"
- name: Report failed deployment
if: failure() && !cancelled()
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
GITHUB_TOKEN: ${{ inputs.github-token }}
DEPLOY_CANCELLED: 'false'
run: bun run "$ACTION_PATH/src/report.ts"
- name: Report cancelled deployment
if: cancelled()
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
GITHUB_TOKEN: ${{ inputs.github-token }}
DEPLOY_CANCELLED: 'true'
run: bun run "$ACTION_PATH/src/report.ts"
26 changes: 26 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions examples/counter/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
.env*
*.tar
Loading
Loading