Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8731438
Changes auto-committed by Conductor
justin808 Oct 17, 2025
a810cca
Fix Ruby version mismatch for CI
justin808 Oct 17, 2025
9fca254
Add postinstall script to build shakapacker from GitHub branch
justin808 Oct 17, 2025
eb1b3be
Update lock files for shakapacker branch changes
justin808 Oct 17, 2025
9930aaf
Enable early hints debug mode
justin808 Oct 17, 2025
395b7a4
Update shakapacker to latest from early-hints branch
justin808 Oct 17, 2025
58f9d8e
Update shakapacker to latest from early-hints branch
justin808 Oct 17, 2025
8082229
Update shakapacker to latest from early-hints branch
justin808 Oct 17, 2025
0104fb1
Enable Puma early hints support in all Procfiles
justin808 Oct 17, 2025
56d2a68
Keep --early-hints only for production and testing
justin808 Oct 17, 2025
b6255d5
Enable early hints in Control Plane production deployment
justin808 Oct 18, 2025
4eed1fe
Add Thruster HTTP/2 proxy for improved performance
justin808 Nov 2, 2025
9d7d4a0
Remove yarn.lock
justin808 Nov 2, 2025
5a85c1c
Update to shakapacker 9.3.0 release
justin808 Nov 2, 2025
d46f274
Add comprehensive Thruster documentation and UI indicators
justin808 Nov 3, 2025
493fc04
Update UI to indicate early hints and HTTP/2 enabled on Control Plane
justin808 Nov 5, 2025
eea187f
Fix Thruster and HTTP/2 configuration for Control Plane deployment
justin808 Nov 5, 2025
2a51d9a
Fix Thruster and HTTP/2 configuration for Control Plane deployment
justin808 Nov 6, 2025
b8f46a1
Add comprehensive Thruster + HTTP/2 architecture documentation
justin808 Nov 6, 2025
a4edfdb
Document early hints investigation and update UI to reflect reality
justin808 Nov 6, 2025
4beedac
Fix Ruby version mismatch in CI
justin808 Nov 6, 2025
4ad6d60
Fix Ruby version in Gemfile.lock to 3.4.6p62
justin808 Nov 6, 2025
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
3 changes: 2 additions & 1 deletion .controlplane/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@ ENTRYPOINT ["./.controlplane/entrypoint.sh"]

# Default args to pass to the entry point that can be overridden
# For Kubernetes and ControlPlane, these are the "workload args"
CMD ["./bin/rails", "server"]
# Use Thruster HTTP/2 proxy for optimized performance
CMD ["bundle", "exec", "thrust", "bin/rails", "server"]
168 changes: 168 additions & 0 deletions .controlplane/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,174 @@ If you needed to push a new image with a specific commit SHA, you can run the fo
cpflow build-image -a $APP_NAME --commit ABCD
```

## HTTP/2 and Thruster Configuration

This application uses [Thruster](https://github.com/basecamp/thruster), a zero-config HTTP/2 proxy from Basecamp, for optimized performance on Control Plane.

### What is Thruster?

Thruster is a small, fast HTTP/2 proxy designed for Ruby web applications. It provides:
- **HTTP/2 Support**: Automatic HTTP/2 with multiplexing for faster asset loading
- **Asset Caching**: Intelligent caching of static assets
- **Compression**: Automatic gzip/Brotli compression
- **TLS Termination**: Built-in Let's Encrypt support (not needed on Control Plane)

### Control Plane Configuration for Thruster

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

#### 1. Dockerfile CMD (`.controlplane/Dockerfile`)

The Dockerfile must use Thruster to start the Rails server:

```dockerfile
# Use Thruster HTTP/2 proxy for optimized performance
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`)

The workload port should remain as HTTP/1.1:

```yaml
ports:
- number: 3000
protocol: http # Keep as http, not http2
```

**Important:** This may seem counter-intuitive, but here's why:
- **Thruster handles HTTP/2** on the public-facing TLS connection
- **Control Plane's load balancer** communicates with the container via HTTP/1.1
- Setting `protocol: http2` causes a protocol mismatch and 502 errors
- Thruster automatically provides HTTP/2 to end users through its TLS termination

### 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.

This is a common source of confusion when migrating from Heroku. Always ensure your Dockerfile CMD matches your intended startup command.

### Verifying HTTP/2 is Enabled

After deployment, verify HTTP/2 is working:

1. **Check workload logs:**
```bash
cpflow logs -a react-webpack-rails-tutorial-staging
```

You should see Thruster startup messages:
```
[thrust] Starting Thruster HTTP/2 proxy
[thrust] Proxying to http://localhost:3000
[thrust] Serving from ./public
```

2. **Test HTTP/2 in browser:**
- Open DevTools → Network tab
- Load the site
- Check the Protocol column (should show "h2" for HTTP/2)

3. **Check response headers:**
```bash
curl -I https://your-app.cpln.app
```
Look for HTTP/2 indicators in the response.

### Troubleshooting

#### Workload fails to start

**Symptom:** Workload shows as unhealthy or crashing

**Solution:** Check logs with `cpflow logs -a <app-name>`. Common issues:
- Missing `thruster` gem in Gemfile
- Incorrect CMD syntax in Dockerfile
- Port mismatch (ensure Rails listens on 3000)

#### Getting 502 errors after enabling HTTP/2

**Symptom:** Workload returns 502 Bad Gateway with "protocol error"

**Root Cause:** Setting `protocol: http2` in rails.yml causes a protocol mismatch

**Solution:**
1. Change `protocol: http2` back to `protocol: http` in `.controlplane/templates/rails.yml`
2. Apply the template: `cpflow apply-template rails -a <app-name>`
3. The workload will immediately update (no redeploy needed)

**Why:** Thruster provides HTTP/2 to end users, but Control Plane's load balancer communicates with containers via HTTP/1.1. Setting the port protocol to `http2` tells the load balancer to expect HTTP/2 from the container, which Thruster doesn't provide on the backend.

#### Assets not loading or CORS errors

**Symptom:** Static assets return 404 or fail to load

**Solution:**
- Ensure `bin/rails assets:precompile` runs in Dockerfile
- Verify `public/packs/` directory exists in container
- Check Thruster is serving from correct directory

### Performance Benefits

With Thruster and HTTP/2 enabled on Control Plane, you should see:
- **20-30% faster** initial page loads due to HTTP/2 multiplexing
- **40-60% reduction** in transfer size with Brotli compression
- **Improved caching** of static assets
- **Lower server load** due to efficient asset serving

For detailed Thruster documentation, see [docs/thruster.md](../docs/thruster.md).

### Key Learnings: Thruster + HTTP/2 Architecture

This section documents important insights gained from deploying Thruster with HTTP/2 on Control Plane.

#### Protocol Configuration is Critical

**Common Mistake:** Setting `protocol: http2` in the workload port configuration
**Result:** 502 Bad Gateway with "protocol error"
**Correct Configuration:** Use `protocol: http`

#### Why This Works

Control Plane's architecture differs from standalone Thruster deployments:

**Standalone Thruster (e.g., VPS):**
```
User → HTTPS/HTTP2 → Thruster → HTTP/1.1 → Rails
(Thruster handles TLS + HTTP/2)
```

**Control Plane + Thruster:**
```
User → HTTPS/HTTP2 → Control Plane LB → HTTP/1.1 → Thruster → HTTP/1.1 → Rails
(LB handles TLS) (protocol: http) (HTTP/2 features)
```

#### What Thruster Provides on Control Plane

Even with `protocol: http`, Thruster still provides:
- ✅ Asset caching and compression
- ✅ Efficient static file serving
- ✅ Early hints support
- ✅ HTTP/2 multiplexing features (via Control Plane LB)

The HTTP/2 protocol is terminated at Control Plane's load balancer, which then communicates with Thruster via HTTP/1.1. Thruster's caching, compression, and early hints features work regardless of the protocol between the LB and container.

#### Debugging Tips

If you encounter 502 errors:
1. Verify Thruster is running: `cpln workload exec ... -- cat /proc/1/cmdline`
2. Test internal connectivity: `cpln workload exec ... -- curl localhost:3000`
3. Check protocol setting: Should be `protocol: http` not `http2`
4. Review workload logs: `cpln workload eventlog <workload> --gvc <gvc> --org <org>`

## Other notes

### `entrypoint.sh`
Expand Down
2 changes: 2 additions & 0 deletions .controlplane/templates/rails.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ spec:
ports:
- number: 3000
protocol: http
# Note: Keep as 'http' - Thruster handles HTTP/2 on the TLS frontend,
# but the load balancer communicates with the container via HTTP/1.1
defaultOptions:
# Start out like this for "test apps"
autoscaling:
Expand Down
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.4.6"

gem "react_on_rails", "16.1.1"
gem "shakapacker", "9.3.0.beta.2"
gem "shakapacker", "9.3.0"

# Bundle edge Rails instead: gem "rails", github: "rails/rails"
gem "listen"
Expand All @@ -15,6 +15,7 @@ gem "rails", "~> 8.0"
gem "pg"

gem "puma"
gem "thruster"

# Use SCSS for stylesheets
gem "sass-rails"
Expand Down
Loading