
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Migrating a monolithic framework to container orchestration introduces specific challenges around process management, asset compilation, and stateful background jobs. To successfully run Ruby on Rails on Kubernetes, you must treat the application not as a single server but as a collection of distinct workloads: web servers, background workers, and cron-like schedulers. This guide provides the exact configuration patterns I use in production to ensure stability, security, and audit readiness for Rails applications on modern clusters.
How do you optimize a Dockerfile to run Ruby on Rails on Kubernetes efficiently?
The foundation of any stable Kubernetes deployment is a lean, reproducible container image. When you run Ruby on Rails on Kubernetes, image size directly impacts scaling speed and node costs. A 1.5GB image takes significantly longer to pull during a scale-out event than a 350MB one. Multi-stage builds are mandatory for production Rails containers in 2026.
Multi-stage build pattern
Separate your build dependencies (Node.js, Yarn, build-essential) from your runtime environment. The final stage should contain only the compiled assets, bundled gems, and the Ruby runtime. This reduces the attack surface—a critical requirement for SOC 2 compliance—and minimizes cold-start latency.
# syntax=docker/dockerfile:1
FROM ruby:3.3-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential libpq-dev nodejs npm git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true \
&& bundle config set --local without 'development test' \
&& bundle install --jobs 4
COPY package.json yarn.lock ./
RUN npm install -g yarn && yarn install --frozen-lockfile
COPY . .
RUN SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile
FROM ruby:3.3-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 curl tini \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app /app
COPY --from=builder /usr/local/bundle /usr/local/bundle
ENV RAILS_ENV=production \
RAILS_LOG_TO_STDOUT=true \
RAILS_SERVE_STATIC_FILES=true
EXPOSE 3000
ENTRYPOINT ["/app/bin/docker-entrypoint"]
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"] This pattern keeps the final image under 400MB while ensuring all assets are precompiled. Note the use of SECRET_KEY_BASE_DUMMY during asset compilation; Rails 7+ requires this environment variable even when compiling static assets that don't touch the database.
How do you configure Puma and health checks when you run Ruby on Rails on Kubernetes?
Puma is the standard application server for Rails, but its default configuration will cause failures in Kubernetes. The orchestrator needs to know exactly when a pod can accept traffic. Without proper probes, you will see intermittent 502 errors during deployments and scaling events. Refer to my guide on Kubernetes resource limits and requests for complementary tuning advice.
Puma thread and worker tuning
Kubernetes pods have hard memory boundaries. Unlike a traditional VM where unused RAM is just wasted, in K8s it triggers OOMKilled restarts. Configure Puma to respect container limits:
- Workers: Set to 2–4 for most API/web workloads. Each worker forks a new process with shared memory via copy-on-write.
- Threads: Match your database connection pool size. If
pool: 5in database.yml, set max threads to 5. - Preload: Enable
preload_app!to maximize memory savings through COW, but ensure you reconnect DB and Redis in anon_worker_bootblock.
Liveness vs. Readiness probes
A common mistake is using the same endpoint for both probes. Your readiness probe should verify that Rails can serve requests (DB connected, cache reachable), while liveness should only check if the process is alive. If your readiness probe fails, K8s stops sending traffic. If liveness fails, K8s kills the pod.
# config/puma.rb
max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5)
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
workers ENV.fetch("WEB_CONCURRENCY", 2)
preload_app!
port ENV.fetch("PORT", 3000)
environment ENV.fetch("RAILS_ENV", "production")
on_worker_boot do
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end
# Dedicated health endpoint
plugin :tmp_restart In your Kubernetes manifest, configure the probes to hit /up (Rails 7.1+ default) or a custom controller that checks downstream dependencies for readiness, but only checks process existence for liveness.
How do you manage Sidekiq workers and secrets when you run Ruby on Rails on Kubernetes?
Background processing is where most Rails-on-Kubernetes deployments fail. Sidekiq must be deployed as a separate Deployment, never as a sidecar or mixed into the web pod. This allows independent scaling: your web tier might need 10 replicas during peak hours while Sidekiq needs only 3, or vice versa during batch processing windows.
Sidekiq Deployment essentials
Sidekiq pods do not need HTTP probes. Instead, use a process-level check or a dedicated gem like sidekiq-status exposed via a minimal Rack endpoint if you want K8s-aware health checks. More importantly, configure graceful shutdown periods. Sidekiq needs time to finish in-flight jobs before the pod terminates.
spec:
terminationGracePeriodSeconds: 60
containers:
- name: sidekiq
image: registry.example.com/rails-app:v2.4.0
command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"]
env:
- name: SIDEKIQ_CONCURRENCY
value: "10"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1000m" Secrets management for compliance
When handling sensitive data in regulated environments, never store credentials in ConfigMaps or base64-encoded Secrets without encryption at rest. For teams needing SOC 2 or ISO 27001 compliance, integrate with external secret stores. I detail secure patterns in Kubernetes secrets management done right. Options include:
- External Secrets Operator: Syncs from AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault into native K8s Secrets.
- Sealed Secrets: Encrypts secrets so they can be safely stored in GitOps repositories.
- Vault Agent Injector: Injects secrets as files at runtime without creating K8s Secret objects.
For Rails specifically, avoid putting master.key in the container image. Mount it as a secret volume or inject RAILS_MASTER_KEY as an environment variable from your external store. This ensures credential rotation doesn't require a redeployment.
What deployment strategy prevents downtime when you run Ruby on Rails on Kubernetes?
Rails applications are stateless at the web layer, making them ideal for rolling updates. However, database migrations complicate zero-downtime deployments. You cannot run migrations inside the main application container's entrypoint; doing so causes race conditions where multiple pods attempt schema changes simultaneously.
The migration Job pattern
Run migrations as a Kubernetes Job that executes before the new Deployment rolls out. This is non-negotiable for production safety. Use Helm hooks (pre-upgrade) or Argo CD sync waves to enforce ordering. Learn more about progressive delivery in blue-green and canary deploys on Kubernetes.
| Strategy | Downtime Risk | Complexity | Best For |
|---|---|---|---|
| Rolling Update + Pre-migrate Job | Low | Medium | Most Rails apps, standard releases |
| Blue/Green | None | High | Critical financial/healthcare systems |
| Canary (Argo Rollouts) | Minimal | High | High-traffic apps needing validation |
| In-place Migration (Entrypoint) | High | Low | Development/staging only |
Backward-compatible migrations
Even with pre-migration Jobs, your new code must work with the old schema during the transition window. Follow the expand-contract pattern:
- Expand: Add new columns/tables without removing old ones. Deploy.
- Migrate: Backfill data, update code to read/write new columns. Deploy.
- Contract: Remove deprecated columns after confirming no code references them. Deploy.
This discipline prevents the "new code expects column X but old pods haven't restarted yet" errors that plague naive Rails Kubernetes deployments.
Operational Checklist for Running Rails on Kubernetes
Successfully operating Rails in this environment requires ongoing discipline beyond initial setup. Ensure these items are addressed before considering your deployment production-ready:
- Log to STDOUT: Never write logs to files inside containers. Use
RAILS_LOG_TO_STDOUT=trueand ship to a centralized stack. - Static file serving: Enable
RAILS_SERVE_STATIC_FILESor offload to CDN/Nginx sidecar. Puma should not serve assets in high-traffic scenarios. - Connection pooling: Use PgBouncer or similar if running >20 pods. Direct Postgres connections from every pod will exhaust max_connections.
- Graceful shutdown: Set
terminationGracePeriodSeconds≥ Puma'sshutdown_timeout+ buffer. Handle SIGTERM properly. - Resource boundaries: Always set both requests and limits. Memory limits prevent noisy neighbors; CPU limits prevent throttling surprises.
- Monitoring: Instrument with OpenTelemetry or Prometheus client gems. Track Puma queue depth, Sidekiq latency, and DB connection wait times.
Next Steps for Your Rails Kubernetes Journey
When you run Ruby on Rails on Kubernetes with these patterns, you gain horizontal scalability, resilient deployments, and infrastructure that passes compliance audits without last-minute scrambles. Start with the multi-stage Dockerfile and separate Sidekiq Deployment, then progressively add external secrets management and advanced deployment strategies as your team matures. If your team needs hands-on guidance implementing these patterns or preparing your Rails infrastructure for SOC 2 certification, reach out to discuss your specific architecture.