Preview Environments for Rails: Automated Per-PR Deployments with Bunnyshell
GuideMarch 20, 202612 min read

Preview Environments for Rails: Automated Per-PR Deployments with Bunnyshell

Why Preview Environments for Rails?

Every Rails team knows the story: you write a migration adding a new column, test it locally against SQLite, push to the shared staging server — and it fails because staging has a different PostgreSQL version with stricter constraints. Or a teammate deployed their Action Cable changes to staging right before your product demo. Or Sidekiq is silently swallowing jobs from two different feature branches fighting over the same Redis namespace.

Preview environments solve this. Every pull request gets its own isolated deployment — Rails app with Puma, PostgreSQL database, Redis for caching and Sidekiq — running in Kubernetes with production-like configuration. Reviewers click a link and see the actual running app, not just the diff.

With Bunnyshell, you get:

  • Automatic deployment — A new environment spins up for every PR
  • Production parity — Same Docker images, same database engine, same background job configuration
  • Isolation — Each PR environment is fully independent, no shared staging conflicts
  • Automatic cleanup — Environments are destroyed when the PR is merged or closed

Choose Your Approach

Bunnyshell supports three ways to set up preview environments for Rails. Pick the one that fits your workflow:

ApproachBest forComplexityCI/CD maintenance
Approach A: Bunnyshell UITeams that want the fastest setup with zero pipeline maintenanceEasiestNone — Bunnyshell manages webhooks automatically
Approach B: Docker Compose ImportTeams already using docker-compose.yml for local developmentEasyNone — import converts to Bunnyshell config automatically
Approach C: Helm ChartsTeams with existing Helm infrastructure or complex K8s needsAdvancedOptional — can use CLI or Bunnyshell UI

All three approaches end the same way: a toggle in Bunnyshell Settings that enables automatic preview environments for every PR. No GitHub Actions, no GitLab CI pipelines to maintain — Bunnyshell adds webhooks to your Git provider and listens for PR events.

Prerequisites: Prepare Your Rails App

Regardless of which approach you choose, your Rails app needs a proper Docker setup and the right configuration for running behind a Kubernetes ingress.

1. Create a Production-Ready Dockerfile

Rails runs with Puma as the web server in production. Here's a Dockerfile for a Rails 7+ app on Ruby 3.3 Alpine:

Dockerfile
1FROM ruby:3.3-alpine AS base
2
3# Install system dependencies
4RUN apk add --no-cache \
5    build-base \
6    postgresql-dev \
7    tzdata \
8    libxml2-dev \
9    libxslt-dev \
10    nodejs \
11    yarn \
12    git \
13    curl
14
15WORKDIR /app
16
17# Install gems
18COPY Gemfile Gemfile.lock ./
19RUN bundle install --without development test --jobs 4 --retry 3
20
21# Copy application code
22COPY . .
23
24# Precompile assets
25RUN RAILS_ENV=production SECRET_KEY_BASE=placeholder \
26    bundle exec rails assets:precompile
27
28# Set permissions
29RUN chown -R nobody:nogroup /app
30USER nobody
31
32EXPOSE 3000
33
34CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

Note: The SECRET_KEY_BASE=placeholder is needed only for asset precompilation at build time. The real secret is provided at runtime via environment variable. Never bake actual secrets into your Docker image.

2. Configure Puma for Kubernetes

Edit config/puma.rb to listen on all interfaces:

Ruby
1# config/puma.rb
2max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
3min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
4threads min_threads_count, max_threads_count
5
6worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
7
8port ENV.fetch("PORT") { 3000 }
9environment ENV.fetch("RAILS_ENV") { "development" }
10pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
11
12workers ENV.fetch("WEB_CONCURRENCY") { 2 }
13preload_app!
14
15on_worker_boot do
16  ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
17end
18
19plugin :tmp_restart

3. Configure Rails for Kubernetes

Rails needs specific settings to work correctly behind a Kubernetes ingress (which terminates TLS). Edit config/environments/production.rb:

Ruby
1# config/environments/production.rb
2Rails.application.configure do
3  # Trust all proxies — K8s ingress terminates TLS
4  config.force_ssl = false  # TLS is terminated at the ingress
5  config.assume_ssl = true  # Tell Rails it's behind HTTPS
6
7  # Allow Bunnyshell-generated hostnames (e.g. app-abc123.bunnyenv.com)
8  # Option 1: Clear all host restrictions (simplest for preview environments)
9  config.hosts.clear
10
11  # Option 2: Allow only Bunnyshell preview domains (more secure)
12  # config.hosts << /.*\.bunnyenv\.com/
13
14  # Standard production settings
15  config.cache_classes = true
16  config.eager_load = true
17  config.consider_all_requests_local = false
18  config.action_controller.perform_caching = true
19
20  # Use Redis for caching (set REDIS_URL env var)
21  config.cache_store = :redis_cache_store, {
22    url: ENV["REDIS_URL"],
23    error_handler: ->(method:, returning:, exception:) {
24      Rails.logger.error "Redis error: #{exception.message}"
25    }
26  }
27
28  config.log_level = :info
29  config.log_tags = [:request_id]
30
31  config.active_job.queue_adapter = :sidekiq
32end

config.force_ssl = false is intentional for Kubernetes deployments. The Bunnyshell ingress controller terminates TLS before traffic reaches your Rails app. Setting force_ssl = true would cause redirect loops. Setting assume_ssl = true ensures Rails generates https:// URLs correctly.

4. Environment Variables

Update your .env.example to include Bunnyshell-friendly defaults:

.env
1RAILS_ENV=production
2SECRET_KEY_BASE=
3
4DATABASE_URL=postgresql://rails:password@postgres:5432/rails_production
5# OR individual vars:
6DB_HOST=postgres
7DB_PORT=5432
8DB_NAME=rails_production
9DB_USER=rails
10DB_PASSWORD=
11
12REDIS_URL=redis://redis:6379/0
13
14RAILS_MAX_THREADS=5
15WEB_CONCURRENCY=2
16PORT=3000

Rails Deployment Checklist

  • Dockerfile uses Ruby 3.3 Alpine with all system dependencies
  • Assets precompiled at build time with rails assets:precompile
  • Puma configured to listen on PORT env var, bound to 0.0.0.0
  • config.force_ssl = false and config.assume_ssl = true for K8s ingress
  • config.hosts.clear to allow Bunnyshell-generated hostnames
  • SECRET_KEY_BASE provided via environment variable (not hardcoded)
  • DATABASE_URL or individual DB env vars configured
  • Redis configured for caching, sessions, and Sidekiq
  • db:migrate will be run post-deploy via CLI or init container

Approach A: Bunnyshell UI — Zero CI/CD Maintenance

This is the easiest approach. You connect your repo, paste a YAML config, deploy, and flip a toggle. No CI/CD pipelines to write or maintain — Bunnyshell automatically adds webhooks to your Git provider and creates/destroys preview environments when PRs are opened/closed.

Step 1: Create a Project and Environment

  1. Log into Bunnyshell
  2. Click Create project and name it (e.g., "Rails App")
  3. Inside the project, click Create environment and name it (e.g., "rails-main")

Step 2: Define the Environment Configuration

Click Configuration in your environment view and paste this bunnyshell.yaml:

YAML
1kind: Environment
2name: rails-preview
3type: primary
4
5environmentVariables:
6  SECRET_KEY_BASE: SECRET["your-secret-key-base"]
7  DB_PASSWORD: SECRET["your-db-password"]
8
9components:
10  # ── Rails Application (Puma) ──
11  - kind: Application
12    name: rails-app
13    gitRepo: 'https://github.com/your-org/your-rails-repo.git'
14    gitBranch: main
15    gitApplicationPath: /
16    dockerCompose:
17      build:
18        context: .
19        dockerfile: Dockerfile
20      environment:
21        RAILS_ENV: production
22        SECRET_KEY_BASE: '{{ env.vars.SECRET_KEY_BASE }}'
23        RAILS_SERVE_STATIC_FILES: 'true'
24        DATABASE_URL: 'postgresql://rails:{{ env.vars.DB_PASSWORD }}@postgres:5432/rails_production'
25        REDIS_URL: 'redis://redis:6379/0'
26        PORT: '3000'
27        RAILS_MAX_THREADS: '5'
28        WEB_CONCURRENCY: '2'
29        RAILS_LOG_TO_STDOUT: 'true'
30      ports:
31        - '3000:3000'
32    hosts:
33      - hostname: 'app-{{ env.base_domain }}'
34        path: /
35        servicePort: 3000
36    dependsOn:
37      - postgres
38      - redis
39
40  # ── PostgreSQL Database ──
41  - kind: Database
42    name: postgres
43    dockerCompose:
44      image: 'postgres:16-alpine'
45      environment:
46        POSTGRES_USER: rails
47        POSTGRES_PASSWORD: '{{ env.vars.DB_PASSWORD }}'
48        POSTGRES_DB: rails_production
49      ports:
50        - '5432:5432'
51
52  # ── Redis (Cache + Sidekiq) ──
53  - kind: Service
54    name: redis
55    dockerCompose:
56      image: 'redis:7-alpine'
57      command: redis-server --appendonly yes
58      ports:
59        - '6379:6379'
60
61volumes:
62  - name: postgres-data
63    mount:
64      component: postgres
65      containerPath: /var/lib/postgresql/data
66    size: 2Gi
67  - name: redis-data
68    mount:
69      component: redis
70      containerPath: /data
71    size: 512Mi

Key architecture notes:

  • RAILS_SERVE_STATIC_FILES: 'true' — Rails serves its own assets in this configuration. For high-traffic apps you'd put a CDN or Nginx in front, but for preview environments this is perfectly fine
  • DATABASE_URL — Rails 6+ supports DATABASE_URL natively in addition to config/database.yml. It's simpler for environment-based configuration
  • RAILS_LOG_TO_STDOUT: 'true' — Ensures logs are captured by Kubernetes and visible in the Bunnyshell UI

Replace your-org/your-rails-repo with your actual repository. Save the configuration.

Step 3: Deploy

Click the Deploy button, select your Kubernetes cluster, and click Deploy Environment. Bunnyshell will:

  1. Build your Rails Docker image from the Dockerfile
  2. Pull PostgreSQL and Redis images
  3. Deploy everything into an isolated Kubernetes namespace
  4. Generate HTTPS URLs automatically with DNS

Monitor the deployment in the environment detail page. When status shows Running, click Endpoints to access your live Rails app.

Step 4: Run Post-Deploy Commands

After deployment, run Rails database setup via the component's terminal in the Bunnyshell UI, or via CLI:

Bash
1export BUNNYSHELL_TOKEN=your-api-token
2bns components list --environment ENV_ID --output json | jq '._embedded.item[] | {id, name}'
3
4# Run migrations
5bns exec COMPONENT_ID -- bundle exec rails db:migrate
6
7# Seed initial data (if applicable)
8bns exec COMPONENT_ID -- bundle exec rails db:seed
9
10# Verify the app is working
11bns exec COMPONENT_ID -- bundle exec rails runner "puts Rails.env"

For ephemeral environments, you can automate migrations by adding a Kubernetes init container or a post-deploy hook. The simplest approach for preview environments is to run db:migrate manually once on the primary environment — ephemeral environments will inherit the database schema on first spin-up.

Step 5: Enable Automatic Preview Environments

This is the magic step — no CI/CD configuration needed:

  1. In your environment, go to Settings
  2. Find the Ephemeral environments section
  3. Toggle "Create ephemeral environments on pull request" to ON
  4. Toggle "Destroy environment after merge or close pull request" to ON
  5. Select the Kubernetes cluster for ephemeral environments

That's it. Bunnyshell automatically adds a webhook to your Git provider (GitHub, GitLab, or Bitbucket). From now on:

  • Open a PR → Bunnyshell creates an ephemeral environment with the PR's branch
  • Push to PR → The environment redeploys with the latest changes
  • Bunnyshell posts a comment on the PR with a link to the live deployment
  • Merge or close the PR → The ephemeral environment is automatically destroyed

Note: The primary environment must be in Running or Stopped status before ephemeral environments can be created from it.


Approach B: Docker Compose Import

Already have a docker-compose.yml for local development? Most Rails projects do. Bunnyshell can import it directly and convert it to its environment format. No manual YAML writing required.

Step 1: Add a docker-compose.yml to Your Repo

If you don't already have one, create docker-compose.yml in your repo root:

YAML
1version: '3.8'
2
3services:
4  rails-app:
5    build:
6      context: .
7      dockerfile: Dockerfile
8    ports:
9      - '3000:3000'
10    environment:
11      RAILS_ENV: development
12      DATABASE_URL: postgresql://rails:secret@postgres:5432/rails_development
13      REDIS_URL: redis://redis:6379/0
14      SECRET_KEY_BASE: development-key-not-for-production
15      RAILS_LOG_TO_STDOUT: 'true'
16    volumes:
17      - .:/app
18    depends_on:
19      postgres:
20        condition: service_healthy
21      redis:
22        condition: service_started
23    command: bundle exec rails server -b 0.0.0.0
24
25  postgres:
26    image: postgres:16-alpine
27    environment:
28      POSTGRES_USER: rails
29      POSTGRES_PASSWORD: secret
30      POSTGRES_DB: rails_development
31    volumes:
32      - postgres-data:/var/lib/postgresql/data
33    ports:
34      - '5432:5432'
35    healthcheck:
36      test: ['CMD', 'pg_isready', '-U', 'rails']
37      interval: 5s
38      retries: 5
39
40  redis:
41    image: redis:7-alpine
42    command: redis-server --appendonly yes
43    volumes:
44      - redis-data:/data
45    ports:
46      - '6379:6379'
47
48  sidekiq:
49    build:
50      context: .
51      dockerfile: Dockerfile
52    command: bundle exec sidekiq
53    environment:
54      RAILS_ENV: development
55      DATABASE_URL: postgresql://rails:secret@postgres:5432/rails_development
56      REDIS_URL: redis://redis:6379/0
57      SECRET_KEY_BASE: development-key-not-for-production
58    depends_on:
59      - postgres
60      - redis
61    volumes:
62      - .:/app
63
64volumes:
65  postgres-data:
66  redis-data:

Step 2: Import into Bunnyshell

  1. Create a Project and Environment in Bunnyshell (same as Approach A, Step 1)
  2. Click Define environment
  3. Select your Git account and repository
  4. Set the branch (e.g., main) and the path to docker-compose.yml (use / if it's in the root)
  5. Click Continue — Bunnyshell parses and validates your Docker Compose file

Bunnyshell automatically detects:

  • All services (rails-app, postgres, redis, sidekiq)
  • Exposed ports
  • Build configurations (Dockerfiles)
  • Volumes
  • Environment variables

It converts everything into a bunnyshell.yaml environment definition.

Important: The docker-compose.yml is only read during the initial import. Subsequent changes to the file won't auto-propagate — edit the environment configuration in Bunnyshell instead.

Step 3: Adjust the Configuration

After import, go to Configuration in the environment view and update:

Replace hardcoded secrets with SECRET["..."] syntax:

YAML
1environmentVariables:
2  SECRET_KEY_BASE: SECRET["your-secret-key-base"]
3  DB_PASSWORD: SECRET["your-db-password"]

Add dynamic URLs using Bunnyshell interpolation:

YAML
# Replace hardcoded localhost URL with dynamic URL
RAILS_HOST: '{{ components.rails-app.ingress.hosts[0] }}'

Switch to production environment:

YAML
1environment:
2  RAILS_ENV: production
3  DATABASE_URL: 'postgresql://rails:{{ env.vars.DB_PASSWORD }}@postgres:5432/rails_production'
4  REDIS_URL: 'redis://redis:6379/0'

Remove local volumes — The volumes: ['.:/app'] bind mounts are for local development (live code reload). Remove them in Bunnyshell — the Docker image already contains the built app.

Step 4: Deploy and Enable Preview Environments

Same as Approach A — click Deploy, then go to Settings and toggle on ephemeral environments.

Best Practices for Docker Compose with Bunnyshell

  • Remove dev-only volumesvolumes: ['.:/app'] is for local development. In Bunnyshell, the built image contains the code
  • Use Bunnyshell interpolation for dynamic values like URLs and hostnames
  • Switch RAILS_ENV from development to production — the imported config will use whatever you had in docker-compose.yml
  • Design for startup resilience — Kubernetes doesn't guarantee service ordering. Use dependsOn in Bunnyshell config and make your Rails app retry database connections on startup (Rails does this gracefully with db:migrate retries)

Approach C: Helm Charts

For teams with existing Helm infrastructure or complex Kubernetes requirements (custom ingress, service mesh, multiple replicas, advanced Sidekiq scaling). Helm gives you full control over every Kubernetes resource.

Step 1: Create a Helm Chart

Structure your Rails Helm chart in your repo:

Text
1helm/rails/
2├── Chart.yaml
3├── values.yaml
4└── templates/
5    ├── deployment.yaml
6    ├── service.yaml
7    ├── ingress.yaml
8    ├── configmap.yaml
9    ├── secret.yaml
10    └── migration-job.yaml

A minimal values.yaml:

YAML
1replicaCount: 1
2
3image:
4  repository: ""
5  tag: latest
6  pullPolicy: IfNotPresent
7
8service:
9  port: 3000
10
11ingress:
12  enabled: true
13  className: bns-nginx
14  host: ""
15
16env:
17  RAILS_ENV: production
18  SECRET_KEY_BASE: ""
19  DATABASE_URL: ""
20  REDIS_URL: ""
21  RAILS_SERVE_STATIC_FILES: "true"
22  RAILS_LOG_TO_STDOUT: "true"
23  PORT: "3000"
24
25sidekiq:
26  enabled: true
27  replicaCount: 1

Step 2: Define the Bunnyshell Configuration

Create a bunnyshell.yaml using Helm components:

YAML
1kind: Environment
2name: rails-helm
3type: primary
4
5environmentVariables:
6  SECRET_KEY_BASE: SECRET["your-secret-key-base"]
7  DB_PASSWORD: SECRET["your-db-password"]
8
9components:
10  # ── Docker Image Build ──
11  - kind: DockerImage
12    name: rails-image
13    context: /
14    dockerfile: Dockerfile
15    gitRepo: 'https://github.com/your-org/your-rails-repo.git'
16    gitBranch: main
17    gitApplicationPath: /
18
19  # ── PostgreSQL via Helm (Bitnami) ──
20  - kind: Helm
21    name: postgres
22    runnerImage: 'dtzar/helm-kubectl:3.8.2'
23    deploy:
24      - |
25        cat << EOF > pg_values.yaml
26          global:
27            storageClass: bns-network-sc
28          auth:
29            username: rails
30            password: {{ env.vars.DB_PASSWORD }}
31            database: rails_production
32        EOF
33      - 'helm repo add bitnami https://charts.bitnami.com/bitnami'
34      - 'helm upgrade --install --namespace {{ env.k8s.namespace }}
35        --post-renderer /bns/helpers/helm/bns_post_renderer
36        -f pg_values.yaml postgres bitnami/postgresql --version 12.12.10'
37      - |
38        PG_HOST="postgres-postgresql.{{ env.k8s.namespace }}.svc.cluster.local"
39    destroy:
40      - 'helm uninstall postgres --namespace {{ env.k8s.namespace }}'
41    start:
42      - 'kubectl scale --replicas=1 --namespace {{ env.k8s.namespace }}
43        statefulset/postgres-postgresql'
44    stop:
45      - 'kubectl scale --replicas=0 --namespace {{ env.k8s.namespace }}
46        statefulset/postgres-postgresql'
47    exportVariables:
48      - PG_HOST
49
50  # ── Rails App via Helm ──
51  - kind: Helm
52    name: rails-app
53    runnerImage: 'dtzar/helm-kubectl:3.8.2'
54    deploy:
55      - |
56        cat << EOF > rails_values.yaml
57          replicaCount: 1
58          image:
59            repository: {{ components.rails-image.image }}
60          service:
61            port: 3000
62          ingress:
63            enabled: true
64            className: bns-nginx
65            host: app-{{ env.base_domain }}
66          env:
67            RAILS_ENV: production
68            SECRET_KEY_BASE: '{{ env.vars.SECRET_KEY_BASE }}'
69            DATABASE_URL: 'postgresql://rails:{{ env.vars.DB_PASSWORD }}@{{ components.postgres.exported.PG_HOST }}/rails_production'
70            REDIS_URL: 'redis://redis:6379/0'
71            RAILS_SERVE_STATIC_FILES: 'true'
72            RAILS_LOG_TO_STDOUT: 'true'
73        EOF
74      - 'helm upgrade --install --namespace {{ env.k8s.namespace }}
75        --post-renderer /bns/helpers/helm/bns_post_renderer
76        -f rails_values.yaml rails-{{ env.unique }} ./helm/rails'
77    destroy:
78      - 'helm uninstall rails-{{ env.unique }} --namespace {{ env.k8s.namespace }}'
79    start:
80      - 'helm upgrade --namespace {{ env.k8s.namespace }}
81        --post-renderer /bns/helpers/helm/bns_post_renderer
82        --reuse-values --set replicaCount=1 rails-{{ env.unique }} ./helm/rails'
83    stop:
84      - 'helm upgrade --namespace {{ env.k8s.namespace }}
85        --post-renderer /bns/helpers/helm/bns_post_renderer
86        --reuse-values --set replicaCount=0 rails-{{ env.unique }} ./helm/rails'
87    gitRepo: 'https://github.com/your-org/your-rails-repo.git'
88    gitBranch: main
89    gitApplicationPath: /helm/rails
90
91  # ── Redis ──
92  - kind: Service
93    name: redis
94    dockerCompose:
95      image: 'redis:7-alpine'
96      command: redis-server --appendonly yes
97      ports:
98        - '6379:6379'

Key: Always include --post-renderer /bns/helpers/helm/bns_post_renderer in your helm commands. This adds labels so Bunnyshell can track resources, show logs, and manage component lifecycle.

Step 3: Deploy and Enable Preview Environments

Same flow: paste the config in Configuration, hit Deploy, then enable ephemeral environments in Settings.


Enabling Preview Environments (All Approaches)

Regardless of which approach you used, enabling automatic preview environments is the same:

  1. Ensure your primary environment has been deployed at least once (Running or Stopped status)
  2. Go to Settings in your environment
  3. Toggle "Create ephemeral environments on pull request" → ON
  4. Toggle "Destroy environment after merge or close pull request" → ON
  5. Select the target Kubernetes cluster

What happens next:

  • Bunnyshell adds a webhook to your Git provider automatically
  • When a developer opens a PR, Bunnyshell creates an ephemeral environment cloned from the primary, using the PR's branch
  • Bunnyshell posts a comment on the PR with a direct link to the running deployment
  • When the PR is merged or closed, the ephemeral environment is automatically destroyed

No GitHub Actions. No GitLab CI pipelines. No maintenance. It just works.

Optional: CI/CD Integration via CLI

If you prefer to control preview environments from your CI/CD pipeline (e.g., for running migrations or seeding test data before notifying reviewers), you can use the Bunnyshell CLI:

Bash
1# Install
2brew install bunnyshell/tap/bunnyshell-cli
3
4# Authenticate
5export BUNNYSHELL_TOKEN=your-api-token
6
7# Create, deploy, and run migrations in one flow
8bns environments create --from-path bunnyshell.yaml --name "pr-123" --project PROJECT_ID --k8s CLUSTER_ID
9bns environments deploy --id ENV_ID --wait
10bns exec COMPONENT_ID -- bundle exec rails db:migrate
11bns exec COMPONENT_ID -- bundle exec rails db:seed --class=DemoSeeder

Remote Development and Debugging

Bunnyshell makes it easy to develop and debug directly against any environment — primary or ephemeral:

Port Forwarding

Connect your local tools to the remote database:

Bash
1# Forward PostgreSQL to local port 15432
2bns port-forward 15432:5432 --component POSTGRES_COMPONENT_ID
3
4# Connect with psql, TablePlus, or any DB tool
5psql -h 127.0.0.1 -p 15432 -U rails rails_production
6
7# Forward Redis to local port 16379
8bns port-forward 16379:6379 --component REDIS_COMPONENT_ID
9redis-cli -p 16379

Execute Rails Commands

Bash
1# Database
2bns exec COMPONENT_ID -- bundle exec rails db:migrate
3bns exec COMPONENT_ID -- bundle exec rails db:migrate:status
4bns exec COMPONENT_ID -- bundle exec rails db:rollback
5
6# Rails console (interactive REPL)
7bns exec COMPONENT_ID -- bundle exec rails console
8
9# Routes inspection
10bns exec COMPONENT_ID -- bundle exec rails routes | grep api
11
12# Cache management
13bns exec COMPONENT_ID -- bundle exec rails runner "Rails.cache.clear"
14
15# Custom tasks
16bns exec COMPONENT_ID -- bundle exec rails my_namespace:my_task
17
18# Check environment
19bns exec COMPONENT_ID -- bundle exec rails runner "puts Rails.env; puts Rails.application.config.cache_store.inspect"

rails console via bns exec gives you a fully interactive Rails REPL connected to the preview environment's database and Redis. This is invaluable for debugging data issues or testing queries against production-like data.

Live Logs

Bash
1# Stream logs in real time
2bns logs --component COMPONENT_ID -f
3
4# Last 200 lines
5bns logs --component COMPONENT_ID --tail 200
6
7# Logs from the last 5 minutes
8bns logs --component COMPONENT_ID --since 5m

Live Code Sync

For active development, sync your local code changes to the remote container in real time:

Bash
1bns remote-development up --component COMPONENT_ID
2# Edit files locally — changes sync automatically
3# When done:
4bns remote-development down

This is especially useful for debugging issues that only reproduce in the Kubernetes environment — you get the fast feedback loop of local development with the infrastructure of production.


Advanced: Background Jobs with Sidekiq

For production Rails apps, you likely need Sidekiq for background job processing. Add a worker component to your bunnyshell.yaml:

YAML
1  # ── Sidekiq Worker ──
2  - kind: Service
3    name: sidekiq
4    gitRepo: 'https://github.com/your-org/your-rails-repo.git'
5    gitBranch: main
6    gitApplicationPath: /
7    dockerCompose:
8      build:
9        context: .
10        dockerfile: Dockerfile
11      command: ['bundle', 'exec', 'sidekiq', '-C', 'config/sidekiq.yml']
12      environment:
13        RAILS_ENV: production
14        SECRET_KEY_BASE: '{{ env.vars.SECRET_KEY_BASE }}'
15        DATABASE_URL: 'postgresql://rails:{{ env.vars.DB_PASSWORD }}@postgres:5432/rails_production'
16        REDIS_URL: 'redis://redis:6379/0'
17        RAILS_LOG_TO_STDOUT: 'true'
18    dependsOn:
19      - postgres
20      - redis

For Sidekiq Web UI (monitoring dashboard), add it to your routes and expose it via a separate host:

Ruby
1# config/routes.rb
2require 'sidekiq/web'
3
4Rails.application.routes.draw do
5  mount Sidekiq::Web => '/sidekiq'
6  # ... other routes
7end

Advanced: Action Cable with Redis

If your app uses Action Cable (WebSockets), configure it to use Redis as the adapter:

YAML
1# config/cable.yml
2production:
3  adapter: redis
4  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
5  channel_prefix: myapp_production

The REDIS_URL env var set in your Bunnyshell config will be automatically picked up.


Troubleshooting

IssueSolution
"Blocked host" errorconfig.hosts is blocking the Bunnyshell preview URL. Add config.hosts.clear or config.hosts << /.*\.bunnyenv\.com/ to config/environments/production.rb.
Mixed content / HTTPS errorsSet config.force_ssl = false and config.assume_ssl = true. The K8s ingress terminates TLS — Rails sees plain HTTP internally.
Asset 404sEnsure RAILS_SERVE_STATIC_FILES=true is set. Assets must be precompiled in the Dockerfile (rails assets:precompile).
Database connection refusedCheck DATABASE_URL points to the postgres component (not localhost). Wait for the environment to show Running before running migrations.
SECRET_KEY_BASE missingGenerate with rails secret and add as SECRET["your-value"] in environment variables. Never hardcode or bake into the image.
Sidekiq not processing jobsEnsure REDIS_URL is set correctly. Verify config.active_job.queue_adapter = :sidekiq in production.rb. Check Sidekiq component logs.
Puma crashes on bootUsually a missing env var or failed database connection. Check container logs: bns logs --component COMPONENT_ID. Run bns exec COMPONENT_ID -- bundle exec rails runner "puts 'ok'" to test.
Migrations failRun bns exec COMPONENT_ID -- bundle exec rails db:migrate:status to see pending migrations. Check DATABASE_URL syntax — it must be postgresql:// not postgres:// for pg gem compatibility.
Action Cable not connectingVerify REDIS_URL is set and accessible. Check config/cable.yml uses redis adapter in production. Ensure WebSocket upgrade headers pass through the ingress.
Slow asset loadingAsset precompilation ran with placeholder SECRET_KEY_BASE. This is normal — assets are still valid. For faster serving, consider a CDN or Nginx in front.
OOM / container restartReduce WEB_CONCURRENCY (Puma workers). Each worker forks the whole Ruby process. Start with WEB_CONCURRENCY=2 and RAILS_MAX_THREADS=5.
522 Connection timed outCluster may be behind a firewall. Verify Cloudflare IPs are whitelisted on the ingress controller.

What's Next?

  • Add Sidekiq Web UI — Monitor your background jobs at /sidekiq with HTTP Basic Auth for security
  • Add Mailcatcher — Test emails in preview environments with sj26/mailcatcher as a Service component
  • Seed test data — Run bns exec <ID> -- bundle exec rails db:seed --class=DemoSeeder post-deploy
  • Add MinIO — S3-compatible object storage for Active Storage file uploads (minio/minio as a Service component)
  • Add Bullet — N+1 query detection with BULLET_ENABLED=true in preview environments only

Ship faster starting today.

14-day full-feature trial. No credit card required. Pay-as-you-go from $0.007/min per environment.