Agent skill

async-falcon-rails

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.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/async-falcon-rails

SKILL.md

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:

  1. The project is a Rails application (check for Gemfile, config/application.rb)
  2. The project structure includes config/environments/ directory
  3. Bundle is available (bundle command works)
  4. 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:

bash
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" (provides Async::Redis::Endpoint for 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):

dockerfile
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:

dockerfile
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:

ruby
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:

ruby
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:

ruby
# 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:

  1. At the top of the file, after require "rails/all", add:
ruby
require "async/cable"
  1. Inside the class Application < Rails::Application block, after config.load_defaults, add:
ruby
# 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:

bash
bin/rails generate channel BaseChannel

This creates:

  • app/channels/application_cable/channel.rb
  • app/channels/application_cable/connection.rb
  • app/channels/base_channel.rb
  • test/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:

ruby
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:

yaml
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:

yaml
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:

yaml
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_data prevents conflicts with other services (e.g., PostgreSQL)

11.3: Add REDIS_URL Environment Variable

In the env: section under clear:, add:

yaml
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:

yaml
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:

  1. Gemfile: Check that Puma is removed and all async gems are added
  2. Dockerfile: Verify libssl-dev is included in the apt-get install line
  3. Initializers: Verify config/initializers/async_job.rb exists and includes endpoint configuration (endpoint: redis_endpoint)
  4. Environments: Confirm async_job queue adapter in development.rb and production.rb
  5. Application: Verify async/cable require and fiber isolation config in application.rb
  6. Cable: Check that Action Cable is mounted in routes.rb
  7. Cable Config: Confirm cable.yml uses Redis for development and production
  8. 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-server or 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-dev system dependency in Dockerfile
  • Solution: Add libssl-dev to the apt-get install line in Dockerfile (see Step 2)
  • Verification: Check that the Dockerfile includes libssl-dev in the package list alongside build-essential, git, libyaml-dev, and pkg-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 update to 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 ping should return PONG)
  • 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: with init: true to the job server configuration in deploy.yml (see Step 11.1)
  • Why it works: init: true tells 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

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results