Skip to content
Merged
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
7 changes: 4 additions & 3 deletions .controlplane/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ RUN SECRET_KEY_BASE=precompile_placeholder bundle exec rake react_on_rails:gener
# For Kubernetes and ControlPlane, this is the command on the workload.
ENTRYPOINT ["./.controlplane/entrypoint.sh"]

# Run the Pro Node renderer and Rails in a single container (Option 1 from
# docs/oss/building-features/node-renderer/container-deployment.md).
# Default local/single-container command. Control Plane workload templates
# override this with Rails-only and renderer-only commands so deploy_order can
# roll the renderer before Rails.
# `wait -n` exits on first child failure → container exits → Control Plane
# restarts the whole unit. Keeps the existing Thruster HTTP/2 proxy for Rails.
# restarts the whole unit when a workload intentionally uses this default.
Comment thread
justin808 marked this conversation as resolved.
CMD ["bash", "-c", "node renderer/node-renderer.js & RENDERER_PID=$! ; bundle exec thrust bin/rails server & RAILS_PID=$! ; wait -n ; exit 1"]
38 changes: 25 additions & 13 deletions .controlplane/controlplane.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,35 @@ aliases:
default_location: aws-us-east-2
# Configure the workload name used as a template for one-off scripts, like a Heroku one-off dyno.
one_off_workload: rails

# Templates used when bootstrapping review, staging, or production apps.
# Keep node-renderer before rails so first-time setup matches deploy_order.
setup_app_templates:
# GVC template contains the identity and shared environment.
- app

# Resources
- postgres
- redis

# Workloads, like Dyno types on Heroku
- daily-task
- node-renderer
- rails
Comment on lines +34 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor inconsistency worth a one-line clarifying comment: setup_app_templates orders workloads as daily-task, node-renderer, rails, while app_workloads/deploy_order order them node-renderer, rails, daily-task. The comment above only explains "node-renderer before rails" — it'd help future readers to note explicitly that daily-task's position relative to the other two doesn't matter (since it's a cron workload with no startup dependency on the renderer), so there's no drift between the three lists' intents.


# Like the entries in the Heroku Procfile that get deployed when the application code changes
# and the application image updates.
app_workloads:
- node-renderer
- rails
- daily-task

# Deploy the separate React on Rails renderer first, wait for it to become
# ready, then deploy Rails and cron workloads that share the same image.
deploy_order:
- [node-renderer]
- [rails, daily-task]

# Additional workloads that are not affected by deploy-image and promote-app-from-upstream
# These workloads apply to the ps commands
additional_workloads:
Expand Down Expand Up @@ -54,21 +78,9 @@ apps:
<<: *common
# QA Apps are like Heroku review apps, but they use `prefix` so you can run a command like
# this to create a QA app for the tutorial app.
# `cpflow setup gvc postgres redis rails -a qa-react-webpack-rails-tutorial-1234`
# `cpflow setup-app -a qa-react-webpack-rails-tutorial-1234`
qa-react-webpack-rails-tutorial:
<<: *common
# Order matters!
setup_app_templates:
# GVC template contains the identity
- app

# Resources
- postgres
- redis

# Workloads, like Dynos types on Heroku
- daily-task
- rails
# match_if_app_name_starts_with is used to identify these "qa" apps.
match_if_app_name_starts_with: true
image_retention_days: 5
Expand Down
8 changes: 6 additions & 2 deletions .controlplane/docs/cpflow-vs-terraform.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,15 @@ resource "cpln_gvc" "app" {
SECRET_KEY_BASE = "placeholder_secret_key_base_for_test_apps_only"
RENDERER_PORT = "3800"
RENDERER_LOG_LEVEL = "info"
RENDERER_WORKERS_COUNT = "2"
RENDERER_URL = "http://localhost:3800"
RENDERER_WORKERS_COUNT = "1"
RENDERER_SERVER_BUNDLE_CACHE_PATH = "/app/renderer/.node-renderer-bundles"
RENDERER_URL = "http://node-renderer.${var.app_name}.cpln.local:3800"
RENDERER_HOST = "0.0.0.0"
ROLLING_DEPLOY_PREVIOUS_URLS = "https://${var.app_name}.cpln.app/react_on_rails_pro/rolling_deploy"
RSC_SUSPENSE_DEMO_DELAY = "true"
# cpln:// secret references are just strings, so they port over verbatim:
RENDERER_PASSWORD = "cpln://secret/${var.app_name}-secrets.RENDERER_PASSWORD"
ROLLING_DEPLOY_TOKEN = "cpln://secret/${var.app_name}-secrets.ROLLING_DEPLOY_TOKEN"
REACT_ON_RAILS_PRO_LICENSE = "cpln://secret/${var.app_name}-secrets.REACT_ON_RAILS_PRO_LICENSE"
}
}
Expand Down
4 changes: 2 additions & 2 deletions .controlplane/docs/testing-cpflow-github-actions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Testing cpflow GitHub Actions Changes

Generic reusable-workflow behavior belongs upstream in the
[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/v5.1.1/docs/ci-automation.md).
[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/v5.2.0/docs/ci-automation.md).
Use this repo note only as the canary checklist for
`react-webpack-rails-tutorial`.

Expand Down Expand Up @@ -30,7 +30,7 @@ bin/conductor-exec bin/test-cpflow-github-flow ruby /path/to/control-plane-flow/
```

Leave `CPFLOW_VERSION` unset while testing a commit SHA. After the upstream PR
ships in a release tag, repin wrappers to that tag. Use `v5.1.1` for the
ships in a release tag, repin wrappers to that tag. Use `v5.2.0` for the
promotion-hardening and release-runner timeout fixes; use immutable commit SHAs
only for future unreleased upstream PR tests.

Expand Down
86 changes: 72 additions & 14 deletions .controlplane/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ You can see the definition of Postgres and Redis in the `.controlplane/templates

This repo uses the generated `cpflow-*` GitHub Actions wrappers. Keep the
generic behavior documented upstream in the
[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/v5.1.1/docs/ci-automation.md);
[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/v5.2.0/docs/ci-automation.md);
this section only lists the values that are specific to this app.

### Review Apps and Staging
Expand Down Expand Up @@ -124,10 +124,11 @@ The matching Control Plane resources are:
Bootstrap production the same way before the first promotion, using the
production org and production-only secret values. After bootstrap or any
template change, re-apply the persistent production templates so the `rails`
and `daily-task` workloads keep the same secret-backed env names as staging:
`node-renderer`, and `daily-task` workloads keep the same secret-backed env
names as staging:

```sh
cpflow apply-template app postgres redis daily-task rails \
cpflow apply-template app postgres redis daily-task node-renderer rails \
-a react-webpack-rails-tutorial-production \
--org shakacode-open-source-examples-production \
--yes --add-app-identity
Expand All @@ -138,12 +139,13 @@ secrets:

- `SECRET_KEY_BASE`
- `RENDERER_PASSWORD`
- `ROLLING_DEPLOY_TOKEN`
- `REACT_ON_RAILS_PRO_LICENSE`

Generate `SECRET_KEY_BASE` with `openssl rand -hex 64` and
`RENDERER_PASSWORD` with `openssl rand -hex 32`. For real production, prefer
managed Postgres and Redis services and update `DATABASE_URL` and `REDIS_URL`
accordingly.
`RENDERER_PASSWORD` and `ROLLING_DEPLOY_TOKEN` with `openssl rand -hex 32`.
For real production, prefer managed Postgres and Redis services and update
`DATABASE_URL` and `REDIS_URL` accordingly.

Review apps run pull request code, so anything mounted through
`cpln://secret/...` can be read by that code after it starts. Keep the
Expand Down Expand Up @@ -283,9 +285,13 @@ Thruster is a small, fast HTTP/2 proxy designed for Ruby web applications. It pr

To enable Thruster with HTTP/2 on Control Plane, two configuration changes are required:

#### 1. Dockerfile CMD (`.controlplane/Dockerfile`)
#### 1. Container Startup Command

The Dockerfile must use Thruster to start the Rails server:
The Dockerfile `CMD` is the local and single-container fallback. Control Plane
workload templates can override it with explicit `args:`; this app does that for
both `rails` and `node-renderer`.

The fallback command still uses Thruster to start the Rails server:

```dockerfile
# Use Thruster HTTP/2 proxy for optimized performance
Expand All @@ -294,9 +300,9 @@ CMD ["bundle", "exec", "thrust", "bin/rails", "server"]

**Note:** Do NOT use `--early-hints` flag as Thruster handles this automatically.

#### 2. Workload Port Protocol (`.controlplane/templates/rails.yml`)
#### 2. Workload Port Protocol (`.controlplane/templates/rails.yml` and `node-renderer.yml`)

The workload port should remain as HTTP/1.1:
The Rails workload port should remain as HTTP/1.1:

```yaml
ports:
Expand All @@ -310,16 +316,25 @@ ports:
- Setting `protocol: http2` causes a protocol mismatch and 502 errors
- Thruster automatically provides HTTP/2 to end users through its TLS termination

The separate `node-renderer` workload is different: React on Rails Pro's Node
Renderer listens with cleartext HTTP/2 (h2c), so its workload port intentionally
uses `protocol: http2`. Keep its probes as `tcpSocket` because HTTP/1.1-only
health probes cannot speak to that listener.

### Important: Dockerfile vs Procfile

**On Heroku:** The `Procfile` defines how dynos start:
```
web: bundle exec thrust bin/rails server
```

**On Control Plane/Kubernetes:** The `Dockerfile CMD` defines how containers start. The Procfile is ignored.
**On Control Plane/Kubernetes:** workload `args:` define how `rails` and
`node-renderer` start. The Dockerfile `CMD` is the fallback for local or
single-container runs. The Procfile is ignored.

This is a common source of confusion when migrating from Heroku. Always ensure your Dockerfile CMD matches your intended startup command.
This is a common source of confusion when migrating from Heroku. For deployed
workloads, update the matching template in `.controlplane/templates/`; keep the
Dockerfile fallback aligned for local and single-container use.

### Verifying HTTP/2 is Enabled

Expand Down Expand Up @@ -567,17 +582,60 @@ The system uses Control Plane's infrastructure to manage these deployments, with
each review app getting its own resources as defined in the controlplane.yml
configuration.

### Separate React on Rails renderer workload

Control Plane runs the React on Rails Pro Node Renderer as its own
`node-renderer` workload. `.controlplane/controlplane.yml` includes
`node-renderer` in `app_workloads` and sets `deploy_order` so
`cpflow deploy-image` and `cpflow promote-app-from-upstream` deploy the
renderer first, wait for it to become healthy, and then deploy `rails` plus
`daily-task`.

The renderer workload uses the same application image as Rails, runs
`react_on_rails_pro:pre_seed_renderer_cache` at container boot, and then starts
`yarn node-renderer`. Rails gets `RENDERER_URL` from
`.controlplane/templates/app.yml` and reaches the renderer at
`http://node-renderer.<app>.cpln.local:3800`.

`ROLLING_DEPLOY_TOKEN` lives in the app secret dictionary alongside
`RENDERER_PASSWORD`. `ROLLING_DEPLOY_PREVIOUS_URLS` points at the app's
Control Plane rolling-deploy endpoint so the boot seed can pull the draining
bundle before Rails rolls.

### Coordinating existing staging and production apps

For existing persistent apps, roll the topology change separately from the new
React on Rails RC when possible:

1. Populate `ROLLING_DEPLOY_TOKEN` in the staging and production app secret
dictionaries. Use the same value for Rails and the renderer within each app.
2. Re-apply templates to staging first:
`cpflow apply-template app postgres redis daily-task node-renderer rails -a react-webpack-rails-tutorial-staging --org shakacode-open-source-examples-staging --yes --add-app-identity`.
3. Deploy staging with `cpflow deploy-image -a react-webpack-rails-tutorial-staging --org shakacode-open-source-examples-staging`.
cpflow 5.2.0 honors `deploy_order`, so `node-renderer` deploys and waits
before `rails`.
4. After staging is healthy, re-apply the same template list to production.
5. Promote production with the generated workflow. Production promotion also
honors `deploy_order`, so the renderer rolls before Rails.

Do not apply the production template change until the production app secret
dictionary has `ROLLING_DEPLOY_TOKEN`; the app template references that secret
for both Rails and the renderer.

React on Rails docs reference:
<https://reactonrails.com/docs/pro/rolling-deploy-adapters/#deploy-the-renderer-before-rails>


### Updating Generated cpflow Workflows

Keep the reusable-workflow mechanics in the upstream
[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/v5.1.1/docs/ci-automation.md).
[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/v5.2.0/docs/ci-automation.md).
For this repo, the update loop is:

1. Update the bundled `cpflow` gem to the desired release.
2. Refresh generated wrappers from that release with `--staging-branch master`.
3. Keep generated refs on the same release tag as the bundled `cpflow` gem.
This branch pins refs to `v5.1.1`, which includes upstream promotion
This branch pins refs to `v5.2.0`, which includes upstream promotion
hardening and the release-runner timeout fix. Use a full commit SHA only for
short-lived upstream testing and leave `CPFLOW_VERSION` unset in that case.
4. Keep app names and GitHub settings aligned with `.controlplane/controlplane.yml`.
Expand Down
25 changes: 18 additions & 7 deletions .controlplane/shakacode-team.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

Deployments are handled by Control Plane configuration in this repo and GitHub Actions.

### Renderer Workload
- `node-renderer` is an app workload and deploys before `rails` through `deploy_order`.
- `node-renderer` runs the React on Rails Pro boot seed before `yarn node-renderer` so the new renderer cache is warm before Rails rolls.
- Rails reaches the renderer through `RENDERER_URL=http://node-renderer.<app>.cpln.local:3800`.
- Keep `ROLLING_DEPLOY_TOKEN` populated in the app secret dictionary; Rails and the renderer use it for rolling-deploy bundle pulls.
- For existing staging/production apps, populate `ROLLING_DEPLOY_TOKEN`, apply templates with `node-renderer`, validate staging, then promote production. Do not combine the production template cutover with missing secrets.

### Review Apps
- Add a comment `+review-app-deploy` to any PR to deploy a review app
- Leave `REVIEW_APP_PREFIX` unset for the standard path. The workflow infers
Expand All @@ -19,8 +26,8 @@ Deployments are handled by Control Plane configuration in this repo and GitHub A
in this repository.
- Review apps run pull request code. Keep `CPLN_TOKEN_STAGING`,
`qa-react-webpack-rails-tutorial-secrets`, database credentials, renderer
credentials, and license values limited to review/staging use. Never mount
production secrets into review apps.
credentials, rolling-deploy tokens, and license values limited to
review/staging use. Never mount production secrets into review apps.

### Staging Environment
- **Automatic**: Any merge to the `master` branch automatically deploys to staging
Expand Down Expand Up @@ -57,6 +64,10 @@ so those values do not need to be set just to test review apps. Set them only
when testing a fork or clone against a different Control Plane org, review-app
prefix, or public workload.

App secret dictionaries for review, staging, and production must include
`SECRET_KEY_BASE`, `RENDERER_PASSWORD`, `ROLLING_DEPLOY_TOKEN`, and
`REACT_ON_RAILS_PRO_LICENSE`.

Production promotion uses a protected GitHub Environment named `production`:

- Environment secret `CPLN_TOKEN_PRODUCTION`
Expand Down Expand Up @@ -104,20 +115,20 @@ Use `setup-app` for first-time bootstrap because it creates the app secret
policy and identity binding. Use `cpflow apply-template` for later template
updates to existing persistent apps. Production promotion compares both GVC env
names and app workload container env names against staging before copying the
image, so keep production `rails` and `daily-task` env references in sync with
the templates:
image, so keep production `rails`, `node-renderer`, and `daily-task` env
references in sync with the templates:

```sh
cpflow apply-template app postgres redis daily-task rails \
cpflow apply-template app postgres redis daily-task node-renderer rails \
-a react-webpack-rails-tutorial-production \
--org shakacode-open-source-examples-production \
--yes --add-app-identity
```

Advanced optional settings are documented upstream in the
[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/v5.1.1/docs/ci-automation.md).
[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/v5.2.0/docs/ci-automation.md).

Current workflow wrappers pin `control-plane-flow` release tag `v5.1.1`, which
Current workflow wrappers pin `control-plane-flow` release tag `v5.2.0`, which
includes promotion hardening and the release-runner timeout fix. Keep release
tags as the steady-state configuration; use a full commit SHA only for
short-lived upstream testing and leave `CPFLOW_VERSION` unset in that case.
Expand Down
20 changes: 16 additions & 4 deletions .controlplane/templates/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,26 @@ spec:
# set to a secure random value using: openssl rand -hex 64
# Production apps should configure this manually after app creation via a secret.
value: 'placeholder_secret_key_base_for_test_apps_only'
# Pro Node renderer settings. The renderer process runs in the same
# container as Rails (see .controlplane/Dockerfile CMD).
# Pro Node renderer settings. Control Plane runs the renderer in the
# separate node-renderer workload and Rails reaches it over same-GVC DNS.
- name: RENDERER_PORT
value: '3800'
- name: RENDERER_LOG_LEVEL
value: info
- name: RENDERER_WORKERS_COUNT
value: '2'
# One worker keeps the separate tutorial renderer at minimum baseline cost.
value: '1'
Comment thread
justin808 marked this conversation as resolved.
- name: RENDERER_SERVER_BUNDLE_CACHE_PATH
# Shared by the boot-seed rake task and the Node renderer process.
value: /app/renderer/.node-renderer-bundles
- name: RENDERER_URL
value: http://localhost:3800
# Internal same-GVC endpoint for the separate React on Rails Pro Node Renderer workload.
value: http://node-renderer.{{APP_NAME}}.cpln.local:3800
- name: RENDERER_HOST
value: 0.0.0.0
- name: ROLLING_DEPLOY_PREVIOUS_URLS
# The renderer boot seed pulls the still-live bundle manifest before Rails rolls.
value: https://{{APP_NAME}}.cpln.app/react_on_rails_pro/rolling_deploy
# Enable the artificial Suspense demo delay so the streaming fallback is
# visible on the review-app. Off by default in production deployments.
- name: RSC_SUSPENSE_DEMO_DELAY
Expand All @@ -46,6 +56,8 @@ spec:
# pull request code, so every mounted secret must be review-safe.
- name: RENDERER_PASSWORD
value: cpln://secret/{{APP_SECRETS}}.RENDERER_PASSWORD
Comment on lines 57 to 58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing Token Blocks Renderer Boot

When this template is applied to an existing app whose secret dictionary does not yet contain ROLLING_DEPLOY_TOKEN, the new renderer workload inherits an unresolved secret and runs pre_seed_renderer_cache under set -e before opening port 3800. That can keep node-renderer from becoming ready, so deploy_order blocks the Rails rollout until the secret is added.

- name: ROLLING_DEPLOY_TOKEN
value: cpln://secret/{{APP_SECRETS}}.ROLLING_DEPLOY_TOKEN
- name: REACT_ON_RAILS_PRO_LICENSE
value: cpln://secret/{{APP_SECRETS}}.REACT_ON_RAILS_PRO_LICENSE
# Part of standard configuration
Expand Down
Loading
Loading