web-ascender/kamal-previews
Preview environments for Kamal-deployed Rails apps. Open a pull request, get a per-PR Kamal app at a unique URL with a freshly cloned staging database. Close the PR, everything tears down. Single-step entry point — drop one step into a single-job workflow: jobs: preview: runs-on: ubuntu-latest permissions: contents: read packages: write pull-requests: write deployments: write steps: - uses: actions/checkout@v4 - uses: web-ascender/github-actions-kamal-previews@v1 with: base-deploy-file: config/deploy.staging.yml base-secrets-file: .kamal/secrets.staging domain-suffix: preview.example.com deploy-host: staging.example.com database-engine: postgres databases: | DATABASE_URL=myapp_staging:myapp_{db_slug} env: # Forwarded from `secrets.*` in the calling workflow: SSH_PRIVATE_KEY: <your DEPLOY_SSH_KEY secret> # Required only if the secrets file shells out to # `bin/rails credentials:fetch` to populate DATABASE_URL: RAILS_MASTER_KEY: <your STAGING_MASTER_KEY secret> Internally dispatches based on the triggering event: - pull_request opened/synchronize/reopened → deploy - pull_request closed → teardown - delete (branch) → teardown - anything else → skip (with notice) Two consumption forms; pick whichever fits: - **This composite action** — single-step, sibling-friendly. Use when your job needs sibling steps around kamal-previews (e.g. opening a VPN tunnel before SSH to a private deploy host, or a custom credential-fetch step). - **Reusable workflow** at `web-ascender/github-actions-kamal-previews/.github/workflows/preview.yml@v1` — multi-job, with deploy and teardown as separate jobs. Use when you don't need sibling steps and want stricter concurrency control. Both forms support `branch-pattern` and `max-active-previews`.
View on GitHubTrust Signals
- Scorecard Score
- not yet scored
- Maintenance Recency
- Stale
- License
- None
Inputs
| name | description | required | default |
|---|---|---|---|
| base-deploy-file | — | yes | — |
| domain-suffix | — | yes | — |
| deploy-host | — | yes | — |
| database-engine | — | no | none |
| databases | Multi-line list of databases to clone (postgres/mysql only). One entry per line, format `ENV_NAME=source_db:target_pattern`. Tokens in the pattern: `{slug}`, `{db_slug}`, `{base_database}` (= source). Behavior depends on the env-name suffix: - `*_URL` (e.g. `DATABASE_URL`, `QUEUE_DATABASE_URL`): the action clones the DB, sources `base-secrets-file` to read the original URL, swaps the dbname path segment for the per-PR target, and injects the rewritten URL into the deployed app via env.secret. Your database.yml, Rails credentials, and existing secrets file stay untouched. Recommended for most Rails apps. - any other suffix (e.g. `*_NAME`): the resolved DB name is written to env.clear and exported to $GITHUB_ENV. Use when your app reads the name directly or you want to handle URL building yourself. Example (Rails 8 multi-DB): DATABASE_URL=myapp_staging:myapp_{db_slug} QUEUE_DATABASE_URL=myapp_staging_queue:myapp_queue_{db_slug} CACHE_DATABASE_URL=myapp_staging_cache:myapp_cache_{db_slug} | no | "" |
| database-admin-url-var | Name of the variable in `base-secrets-file` to source as the admin connection URL for cloning. Default: `DATABASE_URL`. Ignored when the runner env already has `DATABASE_ADMIN_URL` set, or when no base secrets file is configured. The fallback chain is: 1. `DATABASE_ADMIN_URL` env var (explicit override). 2. The `<database-admin-url-var>` variable read from `base-secrets-file` (e.g. `bin/rails credentials:fetch ...`). 3. Individual `PG_*` / `MYSQL_*` env vars (last-resort fallback). | no | DATABASE_URL |
| sqlite-source-path | — | no | "" |
| sqlite-target-path-pattern | — | no | "" |
| sqlite-also-clone | — | no | "" |
| base-secrets-file | — | no | "" |
| domain-label-pattern | — | no | {slug} |
| service-pattern | — | no | {base_service}-{slug} |
| destination-pattern | — | no | {slug} |
| env-label | — | no | preview |
| env-overrides | — | no | "" |
| env-secret-overrides | — | no | "" |
| deploy-timeout | — | no | "" |
| builder-context | — | no | "" |
| prefix-strip | — | no | feature/,feat/,fix/,bug/,bugfix/,chore/,hotfix/,release/ |
| image-tag | — | no | "" |
| memory-limit | — | no | "" |
| cpu-limit | — | no | "" |
| branch-pattern | Bash-glob pattern; only branches matching it get previews. Empty = every PR. Teardowns ignore the pattern (they always run). | no | "" |
| max-active-previews | Hard cap on concurrent active previews. The deploy step refuses to start a NEW preview when this many are already active (an existing preview being re-deployed doesn't count against the cap). 0 (default) = unlimited. Counts environments named `preview-*` whose latest deployment status isn't `inactive`/`removed`/`failure`/`error`. | no | 0 |
| ruby-version | — | no | 3.3 |
| kamal-version | — | no | "" |
| bundler-cache | Runs `bundle install` (with caching) during setup. Default `true` because the dominant Rails-secrets pattern (`bin/rails credentials:fetch`) needs it. Set to `false` only if you have no Gemfile.lock or your secrets pipeline is bundle-free. | no | true |
| ssh-user | — | no | deploy |
| ssh-port | — | no | 22 |
Outputs
| name | description |
|---|---|
| slug | — |
| db_slug | — |
| destination | — |
| proxy_host | — |
| url | — |
| database_name | — |
| mode | — |