
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
You need to Dockerize a Ruby on Rails application to eliminate environment drift, speed up CI pipelines, and prepare for Kubernetes or cloud-native deployment. While Rails is mature, its dependency tree (native gems, Node.js assets, system libraries) makes naive containerization slow and insecure. This guide provides a battle-tested, multi-stage approach that produces lean, secure images suitable for SOC 2 compliant environments.
How do you structure a multi-stage Dockerfile to Dockerize a Ruby on Rails application?
The most common mistake when teams first attempt to Dockerize a Ruby on Rails application is using a single-stage build. This results in massive images containing compilers, headers, and source code that have no business being in production. A multi-stage build separates the "build" environment from the "runtime" environment, ensuring your final artifact contains only what is strictly necessary to serve requests.
In practice, your Dockerfile should define a builder stage first. This stage installs heavy packages like build-base, postgresql-dev, nodejs, and yarn. You run bundle install and rake assets:precompile here. The second stage, often called runtime, starts fresh from a minimal base image. It copies only the compiled gems, precompiled assets, and application code from the builder. This pattern is essential for keeping images under 300MB and passing security scans.
Optimizing layer caching for Gemfile changes
Docker caches layers sequentially. If you copy your entire application before running bundle install, every code change invalidates the gem cache, forcing a full reinstall. Always copy Gemfile and Gemfile.lock first, install dependencies, and only then copy the rest of the source code. This simple reordering can reduce CI build times from minutes to seconds for typical commits.
What are the best practices to secure and optimize Rails containers?
Security is not optional when you Dockerize a Ruby on Rails application for production. Running as root inside a container is a critical vulnerability; if an attacker escapes the application sandbox, they gain root access to the host namespace. Always create a dedicated rails user with a non-login shell and assign ownership of the application directory. Use USER rails before the entrypoint command.
For optimization, prefer Alpine Linux over Debian-based images. Alpine uses musl libc and BusyBox, resulting in a base image around 5MB versus 80MB+ for slim Debian variants. However, be aware that some native gems require specific compilation flags on musl. Test thoroughly. If you encounter persistent compatibility issues, ruby:3.3-slim is a safe fallback that still offers significant size savings over the default tag.
- Remove cache directories: Run
rm -rf /usr/local/bundle/cache/*.gemafter installation to save space. - Use .dockerignore: Exclude
.git,log/*,tmp/*, andnode_modulesto prevent context bloat and accidental secret leakage. - Pinned versions: Never use
latest. Pin Ruby, Alpine, and Node versions explicitly for reproducible builds. - Health checks: Define a
HEALTHCHECKinstruction hitting/up(Rails 8+ default) or a custom endpoint to enable orchestrator-aware restarts.
How do you configure Docker Compose for local Rails development?
While production uses a single optimized image, local development requires volume mounts for hot reloading and separate services for databases and caches. When you Dockerize a Ruby on Rails application for dev, parity with production matters, but developer experience cannot be sacrificed. Use docker compose (v2 syntax) to orchestrate this stack.
<!-- docker-compose.yml -->
services:
web:
build:
context: .
target: development # Separate target with dev tools
command: ["./bin/rails", "server", "-b", "0.0.0.0"]
volumes:
- .:/app:cached
- bundle_cache:/usr/local/bundle
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
environment:
DATABASE_URL: postgres://postgres:password@postgres/myapp_dev
REDIS_URL: redis://redis:6379/1
postgres:
image: postgres:16-alpine
volumes:
- pg_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
bundle_cache:
pg_data: Note the use of named volumes for bundle_cache. This persists installed gems across container restarts, avoiding redundant reinstalls. The :cached mount option on macOS/Windows improves filesystem performance significantly. For teams working with Docker networking and volumes, understanding these persistence patterns prevents data loss and speeds up onboarding.
How does containerized Rails compare to traditional VPS deployment?
Many teams in Nepal and globally still deploy Rails directly to VPS instances using Capistrano or systemd. While simpler initially, this approach accumulates technical debt. Understanding the trade-offs helps justify the effort to Dockerize a Ruby on Rails application.
| Criteria | Traditional VPS (Capistrano/Systemd) | Containerized (Docker/K8s) |
|---|---|---|
| Environment Parity | Low. Drift occurs as OS packages update independently. | High. Immutable image guarantees identical dev/stage/prod. |
| Onboarding Time | Hours to days. Manual setup scripts often break. | Minutes. docker compose up replicates full stack. |
| Scaling | Vertical or manual horizontal. Slow response to spikes. | Automatic horizontal scaling via HPA in Kubernetes. |
| Rollback Speed | Slow. Requires redeploying previous code revision. | Instant. Re-tag or revert to previous immutable image digest. |
| Security Patching | In-place updates risk breaking app. Requires testing. | Rebuild image with patched base. Zero downtime rollout. |
| Complexity | Low initial, high long-term maintenance. | Higher initial learning curve, lower operational toil. |
For solo developers or small projects, a VPS remains viable. But for any team targeting growth, compliance, or multiple environments, containers provide a foundation that pays dividends. If you are managing databases alongside this transition, review PostgreSQL administration essentials to ensure your containerized DB strategy aligns with backup and replication needs.
How do you handle secrets and environment variables securely?
Never bake secrets into your Docker image. This is a frequent failure point when engineers first Dockerize a Ruby on Rails application. Images are often pushed to registries where they may be scanned or accessed by broader teams. Secrets must be injected at runtime.
In development, use env_file in Docker Compose pointing to a gitignored .env file. In production, leverage your orchestrator’s native secrets management. For Kubernetes, use Secrets mounted as environment variables or files. For AWS ECS, use Secrets Manager integration. For standalone Docker Swarm, use docker secret. Rails 8’s encrypted credentials work well inside containers too, provided the master key is injected securely via environment variable or mounted secret file.
Avoiding common pitfalls with asset compilation
Asset precompilation often fails in containers due to missing Node.js or incorrect paths. Ensure your build stage includes the exact Node version specified in your .nvmrc or package.json. Set RAILS_ENV=production during the build so assets compile with production settings (digests, minification). If using Propshaft or Sprockets, verify that the output directory matches your runtime PUBLIC_ASSETS_PATH. Debugging asset issues in a running container is painful; get it right in the build stage.
Next Steps for Production Readiness
Successfully containerizing your Rails app is just the beginning. To make this truly production-grade, integrate image scanning into your CI pipeline using tools like Trivy or Grype. Automate base image updates to patch vulnerabilities without manual intervention. Consider adopting CI/CD best practices to streamline the path from commit to deployment. If you need help auditing your current container strategy or designing a compliant infrastructure for your Rails platform, reach out to discuss your architecture.