Skip to content

Latest commit

 

History

History
326 lines (239 loc) · 17 KB

File metadata and controls

326 lines (239 loc) · 17 KB

GitHub Actions Flow for Review Apps, Staging, and Production

This document describes the reusable GitHub Actions scaffolding generated by cpflow generate-github-actions.

The goal is to bring the Heroku Flow model into any cpflow project:

  1. Comment /deploy-review-app on a pull request to create or update a review app.
  2. Push more commits to the PR to auto-redeploy that review app.
  3. Push to the staging branch to auto-deploy staging.
  4. Promote the already-built staging artifact to production from the Actions tab.
  5. Let a nightly workflow clean up stale review apps.

Quick Start

End-to-end rollout in one view:

  1. cpflow github-flow-readiness — exits non-zero if the repo is not ready to deploy.
  2. cpflow generate — creates .controlplane/ if missing.
  3. cpflow generate-github-actions — adds the cpflow-* composite actions and workflows.
  4. Configure the GitHub repository secrets and variables the workflows expect.
  5. Push the branch, then comment /deploy-review-app on a PR to spin up a review environment.

See Bootstrap a Project for command details, Repo Readiness Checklist for what "ready" means, and AI Playbook to run the rollout through an agent.

Bootstrap a Project

Run these commands from the project root:

# Check the repo for common rollout blockers before generating files
cpflow github-flow-readiness

# Print the current AI rollout prompt for this repo, if you want to hand it to an agent
cpflow ai-github-flow-prompt

# Create .controlplane/ if it does not exist yet
cpflow generate

# Add reusable GitHub Actions for the Control Plane flow
cpflow generate-github-actions

These local bootstrap commands do not require cpln to be installed yet. Install and log into the Control Plane CLI before any command that talks to the real platform. cpflow github-flow-readiness is the fastest gate: it exits non-zero when the repo is missing a production Dockerfile, missing Rails runtime files, pinned to a legacy Ruby or Bundler toolchain, or depends on exact-pinned gem or npm versions that do not appear to exist in the public registries.

The second command writes namespaced files so they can coexist with an app's existing CI:

  • .github/actions/cpflow-build-docker-image/action.yml
  • .github/actions/cpflow-delete-control-plane-app/action.yml
  • .github/actions/cpflow-delete-control-plane-app/delete-app.sh
  • .github/actions/cpflow-setup-environment/action.yml
  • .github/workflows/cpflow-review-app-help.yml
  • .github/workflows/cpflow-help-command.yml
  • .github/workflows/cpflow-deploy-review-app.yml
  • .github/workflows/cpflow-delete-review-app.yml
  • .github/workflows/cpflow-deploy-staging.yml
  • .github/workflows/cpflow-promote-staging-to-production.yml
  • .github/workflows/cpflow-cleanup-stale-review-apps.yml

cpflow generate also infers the app prefix from the repo directory, infers the Docker base Ruby version from .ruby-version, .tool-versions, or the app's Gemfile, preserves repo-defined frontend precompile hooks such as Shakapacker precompile_hook commands or React on Rails auto bundle generation, and switches to persistent SQLite db and storage templates when config/database.yml shows SQLite in production.

Repo Readiness Checklist

Before generating this flow, confirm that the target repository is already a deployable application rather than a partial sample:

  • the repo can be cloned and installed from scratch with published gem and npm package versions
  • the repo does not depend on unpublished or inaccessible package versions unless the deployment flow also provisions the credentials needed to fetch them
  • the repo is not just a historical generator snapshot pinned to an obsolete Ruby or Bundler toolchain with no validated production build path
  • the app has its real runtime scaffold checked in, for example a complete Rails app with the boot files needed to run bin/rails and bin/dev
  • the repo root maps to one deployable app; multi-app monorepos need a separate rollout decision before using this one-app-per-repo flow
  • the production Dockerfile can build the app's assets and any SSR or renderer bundles that production needs
  • any repo-defined frontend codegen or precompile hooks are preserved before rails assets:precompile
  • the runtime workloads, release command, and required secrets are known well enough to model in .controlplane/

If any of those fail, stop and fix the application first. Do not merge cpflow-* workflows into a repository that is not yet runnable from a clean clone, because the result will be a misleading "deployment flow" for an app that still cannot build or boot.

Required .controlplane/controlplane.yml Structure

The generated workflows assume that .controlplane/controlplane.yml defines:

  • one staging app
  • one review-app prefix with match_if_app_name_starts_with: true
  • one production app with upstream pointing to staging

Typical shape:

aliases:
  common: &common
    cpln_org: my-org-staging
    default_location: aws-us-east-2
    setup_app_templates:
      - app
      - postgres
      - redis
      - rails
    app_workloads:
      - rails
    additional_workloads:
      - postgres
      - redis

apps:
  my-app-staging:
    <<: *common

  my-app-review:
    <<: *common
    match_if_app_name_starts_with: true
    hooks:
      post_creation: bundle exec rails db:prepare
      pre_deletion: bundle exec rails db:drop

  my-app-production:
    <<: *common
    allow_org_override_by_env: false
    allow_app_override_by_env: false
    cpln_org: my-org-production
    upstream: my-app-staging
    release_script: release_script.sh

Important points:

  • REVIEW_APP_PREFIX in GitHub Actions must match the review config key prefix, for example my-app-review.
  • match_if_app_name_starts_with: true is what allows a single config entry to back my-app-review-123, my-app-review-456, and cleanup commands like cpflow cleanup-stale-apps -a my-app-review.
  • upstream: my-app-staging is what lets the production promotion workflow copy the exact staging artifact.
  • If your main web workload is not named rails, set the optional PRIMARY_WORKLOAD repository variable described below.

Required GitHub Repository Settings

Configure these repository secrets:

  • CPLN_TOKEN_STAGING: token for the staging Control Plane org
  • CPLN_TOKEN_PRODUCTION: token for the production Control Plane org

Configure these repository variables:

  • CPLN_ORG_STAGING: staging org name, for example company-staging
  • CPLN_ORG_PRODUCTION: production org name, for example company-production
  • STAGING_APP_NAME: staging GVC name, for example my-app-staging
  • PRODUCTION_APP_NAME: production GVC name, for example my-app-production
  • REVIEW_APP_PREFIX: review-app prefix, for example my-app-review
  • STAGING_APP_BRANCH: optional branch that auto-deploys staging; defaults to main or master if unset
  • PRIMARY_WORKLOAD: optional workload name used to discover the public endpoint and do production health checks; defaults to rails
  • DOCKER_BUILD_EXTRA_ARGS: optional newline-delimited single docker build tokens passed through to cpflow build-image, for example --build-arg=FOO=bar or --secret=id=npmrc,src=.npmrc
  • DOCKER_BUILD_SSH_KNOWN_HOSTS: optional multi-line known_hosts content used with DOCKER_BUILD_SSH_KEY when the build needs SSH access to hosts other than GitHub.com

Recommended org layout:

  • keep review apps and staging in a staging org that developers can access
  • keep production in a separate org with tighter access controls

Optional repository secret for private dependency builds:

  • DOCKER_BUILD_SSH_KEY: private SSH key used when the Dockerfile needs RUN --mount=type=ssh to fetch private GitHub dependencies during image build

Docker Builds with Private Dependencies

Some apps need extra Docker build configuration before the generated workflows are turnkey. Common examples are:

  • pnpm, npm, yarn, or Bundler dependencies pulled from private GitHub repositories
  • Dockerfiles that already use RUN --mount=type=ssh
  • builds that need extra --build-arg, --secret, or related docker build flags

The generated cpflow-build-docker-image action supports this without hardcoding app-specific logic:

  • set DOCKER_BUILD_SSH_KEY if the Docker build needs SSH access to GitHub
  • optionally set DOCKER_BUILD_SSH_KNOWN_HOSTS when the SSH build host is not GitHub.com or you need custom host entries
  • set DOCKER_BUILD_EXTRA_ARGS when you need extra docker build flags

For example, a repo that installs private dependencies from GitHub during Docker build can set:

DOCKER_BUILD_SSH_KEY=<private deploy key secret>
DOCKER_BUILD_SSH_KNOWN_HOSTS=git.example.com ssh-ed25519 AAAA...
DOCKER_BUILD_EXTRA_ARGS=--build-arg=BUNDLE_WITHOUT=development:test

The action will start an SSH agent, add the key, write known_hosts, and pass --ssh=default to cpflow build-image. When DOCKER_BUILD_SSH_KNOWN_HOSTS is unset, the generated action uses pinned GitHub.com host keys by default. If your Dockerfile relies on RUN --mount=type=ssh, validate the build locally with cpflow build-image -a <app> --ssh=default before relying on CI.

Generated Workflow Behavior

cpflow-review-app-help.yml

  • Posts a quick reference when a pull request opens, including on fork-based PRs.

cpflow-help-command.yml

  • Replies to /help on a pull request with the commands and required repo settings.

cpflow-deploy-review-app.yml

  • Creates a review app when someone comments /deploy-review-app.
  • Redeploys an existing review app automatically on later PR pushes.
  • Creates a GitHub deployment and comments with the review URL and logs.
  • Leaves PR pushes alone until the first review app is explicitly requested, which keeps demo-app costs down.
  • Accepts /deploy-review-app only from trusted commenters (OWNER, MEMBER, or COLLABORATOR).
  • Skips fork-based PR deploys because the workflow builds Docker images with repository secrets.

cpflow-delete-review-app.yml

  • Deletes the review app on /delete-review-app.
  • Also deletes it automatically when the pull request closes.
  • Accepts /delete-review-app only from trusted commenters (OWNER, MEMBER, or COLLABORATOR).

cpflow-deploy-staging.yml

  • Builds and deploys the staging app on pushes to STAGING_APP_BRANCH.
  • Falls back to main or master when STAGING_APP_BRANCH is unset.
  • Fails fast when required staging repo settings are missing instead of surfacing opaque cpflow errors.

cpflow-promote-staging-to-production.yml

  • Manually promotes the staging artifact to production with a confirmation input.
  • Verifies that production has the env var names staging expects.
  • Runs a health check against PRIMARY_WORKLOAD.
  • Attempts a rollback of every configured application workload if the new production image does not come up healthy.
  • Creates a GitHub release after a successful promotion.

cpflow-cleanup-stale-review-apps.yml

  • Runs nightly and on demand.
  • Deletes stale review apps using cpflow cleanup-stale-apps.

Composite Actions

The generated workflows share these local composite actions:

  • cpflow-setup-environment: installs Ruby, the Control Plane CLI, and the cpflow gem, then logs into the target org
  • cpflow-build-docker-image: builds and pushes the app image with the desired commit SHA
  • cpflow-delete-control-plane-app: safely deletes temporary apps and refuses to touch names outside the configured review-app prefix

Applying This to React on Rails Demo Apps

This flow is a good fit for the React on Rails demo apps because they already follow the same basic assumptions:

  • the deployable app is a Rails project
  • the primary web workload is usually rails
  • review environments should be temporary and opt-in
  • staging should auto-follow a single branch
  • production should promote the already-tested staging image

In practice, porting the flow into a demo app usually follows five phases.

Before generating:

  1. Confirm the repo passes the readiness checklist above.
  2. Generate .controlplane/ if the app does not have it yet.
  3. Generate the cpflow-* GitHub Actions files.

Verify the generated scaffold:

  1. Update .controlplane/controlplane.yml with staging, review, and production entries.
  2. Confirm that the generated Dockerfile picked a Ruby base image compatible with the app's declared Ruby requirement.
  3. For SQLite-backed apps, confirm that the generated scaffold switched to persistent db and storage volumes, mounted them into the main workload, and added a release script that runs rails db:prepare.

Adapt for the app's runtime:

  1. Keep Node available in the final app image whenever Rails asset compilation or SSR depends on ExecJS or frontend package managers at build or runtime.
  2. Preserve repo-defined frontend precompile hooks, such as Shakapacker precompile_hook commands or React on Rails config.auto_load_bundle = true, before rails assets:precompile.
  3. Add any additional app workloads the app needs at runtime, for example sidekiq, a Node renderer, or any other process type that should deploy the same application image.
  4. Adjust PRIMARY_WORKLOAD only if the public workload is not named rails.

Wire up GitHub secrets, variables, and private builds:

  1. Make sure the repo variables and secrets line up with the configured app names.
  2. If the Dockerfile pulls private dependencies over SSH, configure DOCKER_BUILD_SSH_KEY, add DOCKER_BUILD_SSH_KNOWN_HOSTS when the host is not GitHub.com, and validate that the image can build with RUN --mount=type=ssh.

Validate and push:

  1. Validate the real production Docker build before relying on the workflows, especially if asset compilation or SSR requires Node, extra system packages, multiple processes, extra Docker build flags, or persistent writable paths.
  2. Expect review app deploys to run only for branches in the base repository; fork PRs still get help comments, but deploys are skipped because the workflow uses repository secrets.

AI Playbook

If you want an AI agent to apply this flow to another project, start with cpflow github-flow-readiness, then use the standalone AI rollout prompt. It captures the exact wording, hard stop conditions, and definition of done for this workflow. You can also run cpflow ai-github-flow-prompt from inside the target repo to print the current prompt with that repo's default app prefix already filled in.

Short version:

Set up Control Plane GitHub Flow for this repo. Start with `cpflow github-flow-readiness` and stop on any reported blockers. The repo must be deployable from a clean clone, with published package versions and a production Dockerfile that can really build the app. Stop and report blockers for unpublished packages, inaccessible private dependencies, legacy toolchains, or missing production build paths instead of generating workflows blindly. Then run `cpflow generate` if `.controlplane/` is missing, run `cpflow generate-github-actions`, adapt the generated scaffold to the real workloads, document the required GitHub secrets and variables, validate the real build path locally, push the branch, and check the GitHub Actions results.

Expand that prompt with app-specific requirements before editing files:

  • verify the repo is a real deployable app, not a partial code sample or a demo pinned to unpublished package versions
  • stop and report a scope decision when the repo is a monorepo or contains multiple deployable apps without an already-decided single flow target
  • inspect the production Dockerfile and make sure it can build the app's assets in CI
  • make sure the generated Dockerfile uses a Ruby base image compatible with the app's declared Ruby requirement
  • preserve repo-defined frontend precompile hooks, such as Shakapacker precompile_hook commands or React on Rails config.auto_load_bundle = true
  • keep Node available in the final image if Rails or SSR depends on ExecJS, Yarn, or pnpm after the main npm install layer
  • if config/database.yml shows SQLite in production, confirm that cpflow generate emitted persistent db and storage volumes plus a rails db:prepare release script; otherwise keep the default Postgres workload
  • inspect the production Dockerfile and package sources for private GitHub dependencies, and wire DOCKER_BUILD_SSH_KEY plus DOCKER_BUILD_SSH_KNOWN_HOSTS when the build uses RUN --mount=type=ssh against non-GitHub hosts
  • add extra app_workloads and template files for any runtime sidecars, workers, or renderer processes
  • make sure any sidecar process exposed to sibling workloads binds to 0.0.0.0 instead of container-local localhost
  • make sure sidecar caches or bundle directories live in writable paths for the runtime user, such as tmp/, instead of root-owned image paths
  • keep workflow files generic and put app names, org names, branch names, and Docker build knobs in repository vars and secrets

When the agent applies this to a project, it should avoid hardcoding app names or org names into the workflow files. Those belong in repository vars and secrets.