Preview Environments for Ruby Sinatra: Automated Per-PR Deployments with Bunnyshell
GuideMarch 20, 202611 min read

Preview Environments for Ruby Sinatra: Automated Per-PR Deployments with Bunnyshell

Why Preview Environments for Sinatra?

Sinatra's lightweight nature makes it the go-to choice for microservices, internal APIs, and webhooks in Ruby ecosystems. But "lightweight" doesn't mean simpler to review — a PR that changes a route handler, alters a database schema, or refactors middleware is just as risky to merge without testing as a full Rails PR.

Preview environments solve this. Every pull request gets its own isolated deployment — Sinatra app, PostgreSQL database, and any other services — running in Kubernetes with production-like configuration. Reviewers can hit the API endpoints directly, not just read 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 Puma configuration
  • Isolation — Each PR environment is fully independent, no shared staging conflicts
  • Automatic cleanup — Environments are destroyed when the PR is merged or closed

Sinatra's minimal footprint actually makes preview environments faster to spin up — the Docker build is lean, the startup time is quick, and resource usage per environment is low.

Choose Your Approach

Bunnyshell supports three ways to set up preview environments for Sinatra. 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 Sinatra App

Regardless of which approach you choose, your Sinatra app needs two things: a Dockerfile and the right runtime configuration.

1. Create a Production-Ready Dockerfile

Dockerfile
1FROM ruby:3.3-slim AS base
2
3ENV RACK_ENV=production \
4    BUNDLE_WITHOUT=development:test \
5    BUNDLE_DEPLOYMENT=1
6
7WORKDIR /app
8
9# Install system dependencies
10RUN apt-get update && apt-get install -y --no-install-recommends \
11    build-essential \
12    libpq-dev \
13    && rm -rf /var/lib/apt/lists/*
14
15# Install Ruby dependencies
16COPY Gemfile Gemfile.lock ./
17RUN bundle install --jobs 4 --retry 3
18
19# Copy application code
20COPY . .
21
22EXPOSE 9292
23CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

You need a config/puma.rb in your repo:

Ruby
1# config/puma.rb
2port        ENV.fetch("PORT") { 9292 }
3environment ENV.fetch("RACK_ENV") { "development" }
4workers     ENV.fetch("WEB_CONCURRENCY") { 2 }
5threads     ENV.fetch("RAILS_MAX_THREADS") { 5 }, ENV.fetch("RAILS_MAX_THREADS") { 5 }
6
7preload_app!

The app must listen on 0.0.0.0, not 127.0.0.1. Puma listens on all interfaces by default when you specify a port — but double-check your config. Container networking in Kubernetes requires 0.0.0.0.

2. Configure Sinatra for Kubernetes

Sinatra needs specific configuration to work correctly behind Kubernetes ingress (which terminates TLS):

Ruby
1# app.rb
2require 'sinatra/base'
3require 'sequel'
4require 'rack/ssl'
5
6class MyApp < Sinatra::Base
7  # Listen on all interfaces — required for Kubernetes container networking
8  set :bind, '0.0.0.0'
9  set :port, ENV.fetch('PORT', 9292).to_i
10
11  # Do NOT use Rack::SSL or force SSL — TLS is terminated at the ingress controller.
12  # Kubernetes ingress adds X-Forwarded-Proto: https, but your app sees plain HTTP.
13  # Forcing SSL here will cause infinite redirect loops.
14
15  # Database connection via environment variable
16  DB = Sequel.connect(
17    ENV.fetch('DATABASE_URL') { raise 'DATABASE_URL is not set' }
18  )
19
20  # Session secret from environment
21  use Rack::Session::Cookie,
22      key: '_myapp_session',
23      secret: ENV.fetch('SESSION_SECRET') { raise 'SESSION_SECRET is not set' },
24      same_site: :lax,
25      secure: ENV['RACK_ENV'] == 'production'
26
27  # Common logging middleware
28  use Rack::CommonLogger
29
30  # Health check endpoint — Kubernetes liveness probe
31  get '/health' do
32    content_type :json
33    { status: 'ok', env: settings.environment }.to_json
34  end
35
36  # Your routes here
37  get '/' do
38    content_type :json
39    { message: 'Hello from Sinatra' }.to_json
40  end
41end

Do not use Rack::SSL or use Rack::SSL middleware in your Sinatra app. TLS is terminated at the Kubernetes ingress controller — your app receives plain HTTP traffic. Adding SSL enforcement at the app layer causes infinite redirect loops. Use secure: true only on cookies (Rack handles this correctly by reading X-Forwarded-Proto).

Gemfile

A typical production Gemfile for a Sinatra API with PostgreSQL:

Ruby
1source 'https://rubygems.org'
2
3ruby '3.3.0'
4
5gem 'sinatra', '~> 4.0'
6gem 'sinatra-contrib'          # Namespace, respond_with, etc.
7gem 'puma', '~> 6.4'           # Production web server
8gem 'pg', '~> 1.5'             # PostgreSQL adapter
9gem 'sequel', '~> 5.80'        # Lightweight ORM / query builder
10gem 'rack', '~> 3.0'
11gem 'rack-contrib'
12gem 'dotenv', groups: [:development, :test]
13
14group :development, :test do
15  gem 'rspec'
16  gem 'rack-test'
17end

Sinatra Deployment Checklist

  • set :bind, '0.0.0.0' — required for Kubernetes networking
  • DATABASE_URL loaded from environment variable
  • SESSION_SECRET loaded from environment variable
  • No Rack::SSL middleware — TLS terminated at ingress
  • RACK_ENV=production set in Dockerfile
  • Puma config file at config/puma.rb
  • /health endpoint for Kubernetes liveness/readiness probes
  • Gemfile.lock committed to the repository

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., "Sinatra API")
  3. Inside the project, click Create environment and name it (e.g., "sinatra-main")

Step 2: Define the Environment Configuration

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

YAML
1kind: Environment
2name: sinatra-preview
3type: primary
4
5environmentVariables:
6  SESSION_SECRET: SECRET["your-session-secret"]
7  DB_PASSWORD: SECRET["your-db-password"]
8
9components:
10  # ── Sinatra Application ──
11  - kind: Application
12    name: sinatra-app
13    gitRepo: 'https://github.com/your-org/your-sinatra-repo.git'
14    gitBranch: main
15    gitApplicationPath: /
16    dockerCompose:
17      build:
18        context: .
19        dockerfile: Dockerfile
20      environment:
21        RACK_ENV: production
22        PORT: '9292'
23        DATABASE_URL: 'postgresql://sinatra:{{ env.vars.DB_PASSWORD }}@postgres/sinatra_db'
24        SESSION_SECRET: '{{ env.vars.SESSION_SECRET }}'
25        APP_HOST: '{{ components.sinatra-app.ingress.hosts[0] }}'
26      ports:
27        - '9292:9292'
28    hosts:
29      - hostname: 'app-{{ env.base_domain }}'
30        path: /
31        servicePort: 9292
32    dependsOn:
33      - postgres
34
35  # ── PostgreSQL Database ──
36  - kind: Database
37    name: postgres
38    dockerCompose:
39      image: 'postgres:16-alpine'
40      environment:
41        POSTGRES_DB: sinatra_db
42        POSTGRES_USER: sinatra
43        POSTGRES_PASSWORD: '{{ env.vars.DB_PASSWORD }}'
44      ports:
45        - '5432:5432'
46
47volumes:
48  - name: postgres-data
49    mount:
50      component: postgres
51      containerPath: /var/lib/postgresql/data
52    size: 1Gi

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

APP_HOST is passed as an environment variable so your Sinatra app can generate absolute URLs in responses or redirects. Use ENV['APP_HOST'] in your app where needed — e.g., for OAuth callbacks, webhook URLs, or CORS headers.

Step 3: Deploy

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

  1. Build your Sinatra Docker image from the Dockerfile
  2. Pull the PostgreSQL image
  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 Sinatra API.

Step 4: Run Database Migrations

After deployment, run your database migrations via the component terminal in the Bunnyshell UI, or via CLI:

Bash
1export BUNNYSHELL_TOKEN=your-api-token
2
3# Get the component ID for sinatra-app
4bns components list --environment ENV_ID --output json | jq '._embedded.item[] | {id, name}'
5
6# Run Sequel migrations
7bns exec COMPONENT_ID -- bundle exec sequel -m db/migrations $DATABASE_URL
8
9# Or if using ActiveRecord-style rake tasks
10bns exec COMPONENT_ID -- bundle exec rake db:migrate

For Sequel migrations, create your migrations in db/migrations/:

Ruby
1# db/migrations/001_create_users.rb
2Sequel.migration do
3  up do
4    create_table(:users) do
5      primary_key :id
6      String :email, null: false, unique: true
7      String :name
8      DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
9    end
10  end
11
12  down do
13    drop_table(:users)
14  end
15end

Step 5: Enable Automatic Preview Environments

This is the key 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

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? 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  sinatra-app:
5    build:
6      context: .
7      dockerfile: Dockerfile
8    ports:
9      - '9292:9292'
10    environment:
11      RACK_ENV: development
12      PORT: '9292'
13      DATABASE_URL: 'postgresql://sinatra:sinatra@postgres/sinatra_dev'
14      SESSION_SECRET: 'local-dev-session-secret-change-in-production'
15      APP_HOST: 'localhost:9292'
16    depends_on:
17      - postgres
18
19  postgres:
20    image: postgres:16-alpine
21    environment:
22      POSTGRES_DB: sinatra_dev
23      POSTGRES_USER: sinatra
24      POSTGRES_PASSWORD: sinatra
25    volumes:
26      - postgres-data:/var/lib/postgresql/data
27    ports:
28      - '5432:5432'
29
30volumes:
31  postgres-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 (sinatra-app, postgres)
  • Exposed ports
  • Build configurations (Dockerfiles)
  • Volumes
  • Environment variables

It converts everything into a bunnyshell.yaml environment definition.

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
  • Update APP_HOST and DATABASE_URL to use Bunnyshell interpolation:
YAML
1DATABASE_URL: 'postgresql://sinatra:{{ env.vars.DB_PASSWORD }}@postgres/sinatra_db'
2SESSION_SECRET: '{{ env.vars.SESSION_SECRET }}'
3APP_HOST: '{{ components.sinatra-app.ingress.hosts[0] }}'
4RACK_ENV: production

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

  • Use separate env files — Keep .env for local dev and update the Bunnyshell config separately
  • Design for startup resilience — Kubernetes doesn't guarantee depends_on ordering. Make your Sinatra app retry database connections on startup:
Ruby
1# config.ru
2require_relative 'app'
3
4# Retry DB connection on startup (important for Kubernetes)
5retries = 0
6begin
7  DB.test_connection
8rescue Sequel::DatabaseConnectionError => e
9  retries += 1
10  raise if retries > 5
11  $stderr.puts "DB not ready, retrying in #{retries * 2}s... (#{e.message})"
12  sleep retries * 2
13  retry
14end
15
16run MyApp
  • Use Bunnyshell interpolation for dynamic host values:
YAML
1# Local docker-compose.yml
2APP_HOST: localhost:9292
3
4# Bunnyshell environment config (after import)
5APP_HOST: '{{ components.sinatra-app.ingress.hosts[0] }}'

Approach C: Helm Charts

For teams with existing Helm infrastructure or complex Kubernetes requirements. Since Sinatra is often deployed as a microservice within a larger platform, Helm makes it easy to manage ingress rules, service discovery, and resource limits precisely.

Step 1: Create a Helm Chart

Structure your Sinatra Helm chart in your repo:

Text
1helm/sinatra/
2├── Chart.yaml
3├── values.yaml
4└── templates/
5    ├── deployment.yaml
6    ├── service.yaml
7    ├── ingress.yaml
8    └── configmap.yaml

A minimal values.yaml:

YAML
1replicaCount: 1
2image:
3  repository: ""
4  tag: latest
5service:
6  port: 9292
7ingress:
8  enabled: true
9  className: bns-nginx
10  host: ""
11env:
12  RACK_ENV: production
13  PORT: "9292"
14  DATABASE_URL: ""
15  SESSION_SECRET: ""
16  APP_HOST: ""
17livenessProbe:
18  path: /health
19  initialDelaySeconds: 15
20  periodSeconds: 10
21readinessProbe:
22  path: /health
23  initialDelaySeconds: 5
24  periodSeconds: 5

Step 2: Define the Bunnyshell Configuration

Create a bunnyshell.yaml using Helm components:

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

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 custom migration scripts or seeding test data), 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
10
11# Run Sequel migrations
12bns exec COMPONENT_ID -- bundle exec sequel -m db/migrations $DATABASE_URL
13
14# Seed test data
15bns exec COMPONENT_ID -- bundle exec ruby db/seeds.rb

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 or any DB tool
5psql -h localhost -p 15432 -U sinatra sinatra_db
6
7# Run Sequel migrations locally against the remote DB
8DATABASE_URL=postgresql://sinatra:password@localhost:15432/sinatra_db \
9  bundle exec sequel -m db/migrations $DATABASE_URL

Execute Ruby/Rack Commands

Bash
1# Open a Ruby console connected to the running app's environment
2bns exec COMPONENT_ID -- bundle exec irb -r ./app
3
4# Run Sequel migrations
5bns exec COMPONENT_ID -- bundle exec sequel -m db/migrations $DATABASE_URL
6
7# Run seeds
8bns exec COMPONENT_ID -- bundle exec ruby db/seeds.rb
9
10# Check DB migration status
11bns exec COMPONENT_ID -- bundle exec sequel -m db/migrations --check $DATABASE_URL
12
13# Run the test suite against the running environment
14bns exec COMPONENT_ID -- bundle exec rspec

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 to the running container
3# Puma will pick up changes with code reloading (Reloader middleware in dev)
4# When done:
5bns remote-development down

This is particularly useful for Sinatra microservices where you want to test route changes or middleware modifications against a real database without rebuilding the Docker image.


Troubleshooting

IssueSolution
502 Bad GatewaySinatra / Puma isn't listening on 0.0.0.0:9292. Check set :bind, '0.0.0.0' in your app and port in config/puma.rb.
Infinite SSL redirect loopRemove use Rack::SSL or use Rack::SSL::Enforcer. TLS is terminated at ingress — adding SSL enforcement at the app layer causes redirect loops.
SESSION_SECRET is not set crashSet SESSION_SECRET in your environment variables. Use SECRET["..."] syntax for secrets in Bunnyshell.
DATABASE_URL is not set crashVerify DATABASE_URL is defined in the environment config and uses postgres as the hostname (the component name), not localhost.
Connection refused to PostgreSQLUse postgres as the DB hostname (matching the component name in bunnyshell.yaml), not localhost.
404 on all routesMake sure your config.ru runs your Sinatra app class and the class is loaded.
CORS errors from frontendSet Access-Control-Allow-Origin: https://{{ components.sinatra-app.ingress.hosts[0] }} or use the sinatra-cors gem with the APP_HOST env var.
Sequel migration errorsEnsure migrations run after the app starts and the DB is reachable. Use bns exec to run migrations manually after the first deploy.
Service startup order issuesKubernetes doesn't guarantee depends_on ordering. Add DB connection retry logic in config.ru.
522 Connection timed outCluster may be behind a firewall. Verify Cloudflare IPs are whitelisted on the ingress controller.

What's Next?

  • Add Sidekiq for background jobs — Add a Redis component and a Sidekiq worker component for async processing
  • Seed test data — Run bns exec <ID> -- bundle exec ruby db/seeds.rb post-deploy
  • Add Redis for caching — Pass REDIS_URL as an environment variable and use it with Rack::Cache or Dalli
  • Monitor with Sentry — Add the sentry-ruby gem and pass SENTRY_DSN as an environment variable
  • Scale horizontally — Increase replicaCount in Helm values or WEB_CONCURRENCY in the Puma config for load testing

Ship faster starting today.

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