Rails 8 ships Thruster in the default Docker flow. Use it for simple, single-container apps. Keep Nginx when we need edge features.
Puma stays as the app server. Thruster runs in front of Puma and handles the common reverse-proxy duties.
What is Thruster?
Thruster is a lightweight, Go-based HTTP/2 proxy from 37signals. It runs alongside Puma and provides:
- HTTP/2 support
- Optional automatic TLS certificates with Let’s Encrypt
- Basic HTTP caching for public assets
- X-Sendfile support for efficient file serving
- Gzip compression
Before
Before Rails 8, a simple production setup often looked like this:
- Puma as the application server
- Nginx or Apache as a reverse proxy
- Certbot or another TLS certificate flow
- Manual configuration for asset caching and compression
That is a fair setup for complex infrastructure. It is too much ceremony for a small Rails app that just needs to run from one container.
After
Rails 8 includes the thruster gem
and starts the container through bin/thrust
unless Thruster is skipped.
The generated Dockerfile now ends with:
# Start server via Thruster by default, this can be overwritten at runtime
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]With TLS_DOMAIN configured,
the same container can terminate HTTPS too.
Without it, Thruster runs in HTTP-only mode.
How Thruster works
Thruster wraps the Puma process, so we do not need a separate process manager inside the container.
Without Thruster:
bin/rails serverWith Thruster:
bin/thrust bin/rails serverThruster starts first, then launches Puma as a child process. When Puma stops, Thruster stops too.
Key features
HTTP/2 support
Thruster provides HTTP/2 out of the box. That gives us multiplexed connections and header compression without configuring another proxy.
Automatic TLS with Let’s Encrypt
To enable automatic TLS,
set the TLS_DOMAIN environment variable:
TLS_DOMAIN=myapp.com bin/thrust bin/rails serverThis needs port 80 to be reachable so Let’s Encrypt can complete the ACME challenge.
Thruster automatically:
- Provisions SSL certificates from Let’s Encrypt
- Renews certificates before they expire
- Handles HTTPS termination
If TLS_DOMAIN is not set,
Thruster does not provision certificates
and runs in HTTP-only mode.
Asset caching
Thruster can cache public assets and serve them directly. This reduces load on the Rails application for static file requests.
X-Sendfile support
For serving large files, Thruster supports X-Sendfile. When Rails emits an X-Sendfile header, it can hand the file path to Thruster, and Thruster can stream the file instead of making Ruby do the work.
# config/environments/production.rb
config.action_dispatch.x_sendfile_header = "X-Sendfile"class DownloadsController < ApplicationController
def show
send_file Rails.root.join("storage/manual.pdf")
end
endGzip compression
Thruster automatically compresses responses, reducing bandwidth usage and improving page load times.
It also includes a default jitter for compressed responses to reduce BREACH-style information leaks.
Configuration
Thruster is designed to be zero config. Most features are enabled automatically with sensible defaults.
When we need to customize it, we use environment variables:
# Set the domain for SSL certificates
TLS_DOMAIN=myapp.com
# Set Puma's target port
TARGET_PORT=3000
# Set public ports
HTTP_PORT=80
HTTPS_PORT=443
# Tune the cache
CACHE_SIZE=67108864
MAX_CACHE_ITEM_SIZE=1048576
# Disable gzip compression
GZIP_COMPRESSION_ENABLED=falseThruster also accepts these with a THRUSTER_ prefix.
For example, THRUSTER_TLS_DOMAIN
can be used instead of TLS_DOMAIN
to avoid collisions with application environment variables.
Using Thruster in Docker
Rails 8 adds the thruster gem
to the generated Gemfile:
# Add HTTP asset caching/compression and X-Sendfile acceleration to Puma
gem "thruster", require: falseIt also generates bin/thrust
and uses it from the Dockerfile:
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]Skipping Thruster
If we do not want Thruster in a new Rails 8 application, we can skip it during generation:
rails new myapp --skip-thrusterThis creates the application without the thruster gem
and uses a standard Puma CMD in the Dockerfile.
To remove Thruster from an existing Rails 8 application:
- Remove the gem from the Gemfile:
gem "thruster", require: false-
Remove
bin/thrust. -
Update the Dockerfile CMD to use Rails directly:
# Before
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]
# After
EXPOSE 3000
CMD ["./bin/rails", "server"]- Run
bundle installto update the lockfile.
This is useful when we already have a reverse proxy like Nginx or a cloud load balancer (ALB, CloudFront) handling SSL termination, caching, and compression in front of the application.
When to use Thruster
Use Thruster when:
- The app runs on one server or a small set of app containers.
- We control port 80 and want Let’s Encrypt inside the container.
- We want HTTP/2, asset caching, compression, and X-Sendfile without maintaining Nginx config.
Skip Thruster when:
- A CDN or load balancer already handles TLS and static assets.
- We need advanced routing, rewrites, WAF rules, or complex header manipulation.
- We need centralized cache behavior at the edge.
Operational caveats
Thruster is a better default, not a full edge platform.
- Automatic TLS needs port 80 reachable from the public internet for certificate provisioning.
- The asset cache is local to the running process,
so tune
CACHE_SIZEfor larger apps. - If scale requires shared cache behavior, keep that at the CDN or load balancer layer.
Thruster vs Nginx
| Feature | Thruster | Nginx |
|---|---|---|
| Configuration | No config file | Requires config files |
| TLS management | Automatic with TLS_DOMAIN |
Manual, Certbot, or platform-managed |
| HTTP/2 | Built in | Requires configuration |
| Asset Caching | Automatic | Requires configuration |
| Complexity | Minimal | Higher |
| Best fit | Simple Rails deployments | Complex edge and routing setups |
Conclusion
Thruster simplifies Rails production deployments by covering the common reverse proxy tasks inside the default Rails Docker setup.
It is not a replacement for every Nginx setup. It is the better default for the common case: a Rails app, a container, Puma, and fewer moving parts.
