name: async-falcon-rails description: Transform a Rails application to use Falcon web server with async job processing (async-job), async Action Cable, and Redis-compatible database (Valkey for production). Use when the user wants to add async/Falcon stack to a Rails project, migrate from Puma to Falcon, or set up async job processing with Redis for both development and production environments including Kamal deployment.
Async Falcon Rails
Overview
Transform a Rails application to use the Falcon web server with async job processing, async Action Cable, and Redis-compatible database (Valkey for production). This skill handles the complete migration from Puma to Falcon, configures async job adapters, sets up Redis/Valkey for Action Cable and job queues, and configures Kamal deployment for production.
When to Use
Use this skill when the user:
- Wants to add async/Falcon stack to an existing Rails project
- Needs to migrate from Puma to Falcon web server
- Requests async job processing setup with Redis
- Wants to configure async Action Cable
- Needs Kamal deployment configuration for the async stack
Prerequisites
Before applying this skill, verify:
- The project is a Rails application (check for
Gemfile,config/application.rb) - The project structure includes
config/environments/directory - Bundle is available (
bundlecommand works) - If Kamal deployment is needed, check for
config/deploy.yml
Workflow
Follow these steps in order to transform a Rails application to use the async/Falcon stack:
Step 1: Update Gemfile Dependencies
Replace Puma with Falcon and add async dependencies:
bundle remove puma
bundle add falcon
bundle add async-job-processor-redis
bundle add async-job-adapter-active_job
bundle add async-cable
bundle add redis
After running these commands, verify the Gemfile includes:
gem "falcon"gem "async-job-processor-redis"gem "async-job-adapter-active_job"gem "async-cable"gem "redis"(providesAsync::Redis::Endpointfor endpoint parsing)
Step 2: Update Dockerfile for SSL Dependencies
CRITICAL: The async/Falcon stack requires OpenSSL development libraries to build properly. Without this, Docker builds will fail.
Edit the Dockerfile and add libssl-dev to the system dependencies.
Find the line that installs build packages (usually around line 40):
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
Update it to include libssl-dev:
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libyaml-dev libssl-dev pkg-config && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
Why this is needed: The Falcon web server and async gems depend on native extensions that require OpenSSL headers to compile. Without libssl-dev, the Docker build will fail with compilation errors.
Step 3: Create Async Job Configuration
Create config/initializers/async_job.rb with the following content:
require "async/job"
require "async/job/processor/aggregate"
require "async/job/processor/redis"
require "async/job/processor/inline"
require "async/redis/endpoint"
Rails.application.configure do
# Resolve Redis endpoint from REDIS_URL; fallback to localhost for dev/test.
redis_endpoint = Async::Redis::Endpoint.parse(
ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" }
)
config.async_job.define_queue "default" do
enqueue Async::Job::Processor::Aggregate
# Ensure the job runner connects to the accessory container (or localhost in dev).
dequeue Async::Job::Processor::Redis, endpoint: redis_endpoint
end
config.async_job.define_queue "local" do
dequeue Async::Job::Processor::Inline
end
end
This configuration:
- Sets up a "default" queue using Redis for job processing
- Parses the REDIS_URL environment variable to create a proper Redis endpoint
- Passes the endpoint to the Redis processor for both development and production
- Sets up a "local" queue for inline processing
- Uses Aggregate processor for enqueuing and Redis for dequeuing
Step 4: Update Procfile for Development
Update Procfile.dev to include the async job processor:
Add this line to the existing Procfile.dev:
jobs: bundle exec async-job-adapter-active_job-server
The complete Procfile.dev should include at minimum:
web: bin/rails s
jobs: bundle exec async-job-adapter-active_job-server
If the project uses Vite or other frontend build tools, keep those lines as well.
Step 5: Configure Development Environment
Edit config/environments/development.rb to use async_job queue adapter.
Find the section about ActiveJob (usually near config.active_job.verbose_enqueue_logs) and add:
config.active_job.queue_adapter = :async_job
Step 6: Configure Production Environment
Edit config/environments/production.rb to configure both async_job and Redis cache:
Find and update or add these configurations:
# For cache store (usually around line 50)
config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] }
# For queue adapter (usually around line 53)
config.active_job.queue_adapter = :async_job
Step 7: Configure Application for Async/Cable
Edit config/application.rb to add async/cable support:
- At the top of the file, after
require "rails/all", add:
require "async/cable"
- Inside the
class Application < Rails::Applicationblock, afterconfig.load_defaults, add:
# Configure async/fiber support
config.active_record.permanent_connection_checkout = :disallowed
config.active_support.isolation_level = :fiber
These settings enable fiber-based isolation for async operations.
Step 8: Generate Action Cable Base Channel
Run the Rails generator to create the base Action Cable structure:
bin/rails generate channel BaseChannel
This creates:
app/channels/application_cable/channel.rbapp/channels/application_cable/connection.rbapp/channels/base_channel.rbtest/channels/base_channel_test.rb
Step 9: Mount Action Cable in Routes
Edit config/routes.rb to mount the Action Cable server.
Add this line at the top of the Rails.application.routes.draw block:
mount ActionCable.server => '/cable'
Step 10: Configure Cable to Use Redis
Edit config/cable.yml to use Redis for all environments:
Update the configuration to:
development:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: PROJECT_NAME_production
test:
adapter: test
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: PROJECT_NAME_production
Replace PROJECT_NAME with the actual Rails application name (found in config/application.rb as the module name).
Step 11: Configure Kamal Deployment (If Applicable)
If the project uses Kamal for deployment (check for config/deploy.yml), update the deployment configuration:
11.1: Add Job Server
In the servers: section, add or uncomment the job server configuration:
servers:
web:
- 192.168.0.1
job:
hosts:
- 192.168.0.1
cmd: bundle exec async-job-adapter-active_job-server
options:
init: true
Key configuration:
init: true: Skips health checks for the job server (avoids 30-second deployment wait)- Job servers don't expose HTTP endpoints, so health checks would timeout unnecessarily
11.2: Configure Redis/Valkey Accessory
In the accessories: section (create if it doesn't exist), add Redis/Valkey configuration:
accessories:
redis:
image: valkey/valkey:9
host: 192.168.0.1
port: "127.0.0.1:6379:6379"
directories:
- redis_data:/data
Key considerations:
- Important: Use Valkey instead of Redis due to Redis licensing changes. Valkey is a Redis-compatible fork maintained by the Linux Foundation
- Use Valkey 9 or latest stable version
- Port binding
"127.0.0.1:6379:6379"prevents public exposure (localhost only) - Volume name
redis_dataprevents conflicts with other services (e.g., PostgreSQL)
11.3: Add REDIS_URL Environment Variable
In the env: section under clear:, add:
env:
clear:
REDIS_URL: redis://PROJECT_NAME-redis:6379/1
Replace PROJECT_NAME with the service name from the top of deploy.yml. The format follows Kamal's Docker naming convention: {service_name}-{accessory_name}.
11.4: Configure Multi-Architecture Builds
Update the builder: section to support multiple architectures:
builder:
arch:
- amd64
- arm64
This enables building Docker images for:
- amd64: Intel/AMD processors (Windows, Linux, older Macs)
- arm64: Apple Silicon (M1/M2/M3 Macs), ARM-based Linux servers
Verification Steps
After completing the workflow, verify the setup:
- Gemfile: Check that Puma is removed and all async gems are added
- Dockerfile: Verify
libssl-devis included in the apt-get install line - Initializers: Verify
config/initializers/async_job.rbexists and includes endpoint configuration (endpoint: redis_endpoint) - Environments: Confirm
async_jobqueue adapter in development.rb and production.rb - Application: Verify
async/cablerequire and fiber isolation config in application.rb - Cable: Check that Action Cable is mounted in routes.rb
- Cable Config: Confirm cable.yml uses Redis for development and production
- Kamal (if applicable): Verify job server with
init: true, Redis accessory, REDIS_URL, and multi-arch build config in deploy.yml
Important Notes
Redis/Valkey Dependency
This stack requires Redis-compatible database to be running:
- Development: Start Redis locally with
redis-serveror use Docker - Production: Valkey (Redis-compatible) is deployed as a Kamal accessory (configured in deploy.yml)
- Note: We use Valkey instead of Redis in production due to Redis licensing changes. Valkey is a fully Redis-compatible fork maintained by the Linux Foundation
Environment Variables
The REDIS_URL environment variable must be set:
- Development: Defaults to
redis://localhost:6379/1(configured in cable.yml) - Production: Set via Kamal deploy.yml or environment configuration
Kamal Naming Conventions
When using Kamal:
- Redis accessory will be named:
{service_name}-redis - Use this name in REDIS_URL:
redis://{service_name}-redis:6379/1 - Volume names should be descriptive:
redis_data,postgres_data, etc.
Port Binding Security
The Redis port binding "127.0.0.1:6379:6379" ensures:
- Redis is accessible to containers on the same Docker network
- Redis is NOT exposed to the public internet
- Only localhost connections are allowed on the host
Troubleshooting
Docker Build Errors
If Docker build fails with compilation errors about OpenSSL or missing headers:
- Symptom: Build fails during gem installation with errors like "openssl/ssl.h: No such file or directory"
- Cause: Missing
libssl-devsystem dependency in Dockerfile - Solution: Add
libssl-devto the apt-get install line in Dockerfile (see Step 2) - Verification: Check that the Dockerfile includes
libssl-devin the package list alongsidebuild-essential,git,libyaml-dev, andpkg-config
This is a critical dependency for Falcon and async gems - without it, the Docker image cannot be built.
Bundle Errors
If bundle add commands fail:
- Check that Gemfile is not locked with version conflicts
- Try
bundle updateto resolve dependency issues - Verify network connectivity to rubygems.org
Redis Connection Errors
If the application cannot connect to Redis:
- Development: Ensure Redis is running locally (
redis-cli pingshould returnPONG) - Production: Check that REDIS_URL environment variable is set correctly
- Verify cable.yml configuration matches the REDIS_URL format
Kamal Deployment Issues
If Kamal deployment fails:
- Verify all placeholders (PROJECT_NAME, IP addresses) are replaced with actual values
- Check that Redis accessory is running:
kamal accessory details redis - Ensure REDIS_URL matches the Kamal service naming convention
Deployment Timeouts
If Kamal deployment hangs for 30 seconds when deploying the job server:
- Symptom: Deployment waits and then shows "Container not ready" for job server
- Cause: Job server doesn't expose HTTP endpoints, so health checks timeout
- Solution: Add
options:withinit: trueto the job server configuration in deploy.yml (see Step 11.1) - Why it works:
init: truetells Kamal to skip health checks for this service
References
For detailed configuration templates and examples, see:
references/configuration-templates.md- Complete file templates and patterns