Debugging a Rails Docker Deployment: My Journey from LoadError to Success
The Problem That Started Everything
I had just deployed my Rails application to Railway, confident that my Docker container was production-ready. Within seconds of deployment, I was staring at a frustrating error in the Railway logs:
Starting Container
/app/vendor/bundle/ruby/3.3.0/gems/zeitwerk-2.7.3/lib/zeitwerk/core_ext/kernel.rb:34:in `require':
cannot load such file -- debug/prelude (LoadError)
My backend container was failing to start. The application that worked perfectly on my local machine was completely broken in production. This began a systematic debugging journey that taught me valuable lessons about Docker containerization and Rails deployment configurations.
Initial Investigation: Finding the Symptoms
My first instinct was to check what Docker images I had locally to understand what I was working with:
docker images | grep sscorpltd
Output:
sscorpltd/contract-backend latest d6c355d8507c 2 hours ago 1.37GB
sscorpltd/contract-frontend latest 3ff7be29e25a 2 hours ago 81.2MB
I had both frontend and backend images ready, properly tagged with my company namespace. The backend image was substantial at 1.37GB, which made sense given it contained the entire Rails application with all its dependencies. But somewhere in that large image was a problem preventing the container from starting.
Digging Deeper: Understanding the Debug Gem Issue
The error message pointed to a missing file: debug/prelude
. This immediately suggested a problem with the Ruby debug gem. I needed to understand how this gem was configured in my Gemfile:
grep -A 2 -B 2 "debug" Gemfile
Output:
gem "vcr"
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
# Static analysis for security vulnerabilities [https://brakemanscanner.org/]
At first glance, this looked problematic - the debug gem appeared to be in the global scope. However, I needed to see the complete picture of how my Gemfile was structured:
cat Gemfile | grep -n -E "^group|^end|debug"
Output:
75:group :development, :test do
88: # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
89: gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
102:end
104:group :development do
112:end
114:group :production do
120:end
This revealed that the debug gem was actually correctly placed within the :development, :test
group. So why was my production container trying to load it?
Root Cause #1: Incorrect Bundler Configuration
I examined my Dockerfile to understand how gems were being installed:
grep -E "bundle install|RAILS_ENV|ENV|bundle config" Dockerfile
Output:
ENV RAILS_ENV="production" \
RUN bundle install && \
The issue became clear when I looked at the complete ENV configuration in my Dockerfile:
cat Dockerfile
I discovered this configuration:
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development" \
The problem was subtle but critical: BUNDLE_WITHOUT="development"
only excluded the development group, but my debug gem was in a combined :development, :test
group. Since I wasn't excluding test gems, Bundler was still installing the debug gem thinking it was needed for testing.
The First Fix: Correcting the Bundler Configuration
I needed to exclude both development and test groups from the production build. I updated the Dockerfile:
sed -i '' 's/BUNDLE_WITHOUT="development"/BUNDLE_WITHOUT="development:test"/' Dockerfile
This changed the environment variable to properly exclude both groups using the colon separator that Bundler expects.
Root Cause #2: Platform Compatibility Issues
When I attempted to rebuild the Docker image with the corrected configuration, I encountered a new error:
docker build -t sscorp-backend .
Error output:
Your bundle only supports platforms ["x86_64-darwin-24", "x86_64-linux-musl"]
but your local platform is x86_64-linux. Add the current platform to the
lockfile with
`bundle lock --add-platform x86_64-linux` and try again.
This was a classic development environment issue. I had been developing on macOS (darwin platform), and my Gemfile.lock only included platform information for Mac. The Docker container runs on Linux, and Bundler needed to know that it was allowed to install gems for the Linux platform.
The Second Fix: Adding Linux Platform Support
The solution was straightforward - I needed to tell Bundler that my application should support both Mac (for development) and Linux (for production):
bundle lock --add-platform x86_64-linux
Output:
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Writing lockfile to /Users/thanhphongle/Development/projects/sscorp-erp/backend/Gemfile.lock
Building the Corrected Image
With both fixes in place, I rebuilt the Docker image:
docker build -t sscorp-backend .
This time, the build completed successfully:
[+] Building 57.5s (20/20) FINISHED
...
=> [build 3/5] RUN bundle install && ... 28.9s
...
=> => naming to docker.io/library/sscorp-backend:latest
The bundle install step completed without errors, taking 28.9 seconds. The multi-stage Docker build process created an optimized production image that excluded all development and test dependencies.
Preparing for Docker Hub
I tagged the corrected image for Docker Hub deployment:
docker tag sscorp-backend:latest sscorpltd/contract-backend:latest
This created the properly namespaced tag that would allow me to push the image to my organization's Docker Hub repository, replacing the broken version that Railway had been trying to run.
Key Lessons I Learned
Through this debugging journey, I discovered three critical issues that were preventing my Rails application from running in production:
Bundler Configuration Specificity: When excluding gem groups in Docker builds, I learned that I must be precise about which groups to exclude. The syntax
BUNDLE_WITHOUT="development:test"
with a colon separator is required to exclude multiple groups.Platform Compatibility: Developing on Mac and deploying to Linux requires explicit platform declarations in Gemfile.lock. The
bundle lock --add-platform
command is essential for cross-platform compatibility.Debug Gem Dependencies: Even when properly grouped, the debug gem can cause issues if Bundler configuration isn't exact. The gem's
require: "debug/prelude"
directive means it will fail fast if included accidentally.
The Systematic Debugging Approach
What made this debugging session successful was the methodical approach I took:
- Started with the error message and traced it to its source
- Used grep and cat commands to understand file contents without making assumptions
- Examined the Dockerfile to understand the build process
- Made minimal, targeted fixes rather than wholesale changes
- Verified each fix before moving to the next issue
Reflection
This experience reinforced for me that containerization issues often stem from subtle environment differences rather than fundamental code problems. The application worked perfectly in development - it just needed the right configuration to work in production.
The total time from discovering the LoadError to having a working Docker image was about 30 minutes of systematic investigation and targeted fixes. Each error message provided valuable clues, and each CLI command revealed another piece of the puzzle.
Moving forward, I now always ensure my Dockerfiles properly exclude test dependencies and that my Gemfile.lock includes all target platforms before attempting deployment. These small configuration details make the difference between a smooth deployment and hours of debugging.
The satisfaction of seeing that final successful build after working through each issue methodically reminded me why I enjoy the problem-solving aspect of software development. Every error is an opportunity to understand the system more deeply.
CLI Commands Reference: My Debugging Toolkit
During this debugging session, I relied on several CLI commands that proved invaluable for investigating and resolving the issues. Here's my reference list for future debugging sessions:
Docker Image Investigation
# List Docker images with specific namespace filter
docker images | grep sscorpltd
# Build Docker image from Dockerfile
docker build -t sscorp-backend .
# Tag image for Docker Hub repository
docker tag sscorp-backend:latest sscorpltd/contract-backend:latest
Gemfile and Dependency Analysis
# Search for specific gem with context lines
grep -A 2 -B 2 "debug" Gemfile
# Find gem groups and structure with line numbers
cat Gemfile | grep -n -E "^group|^end|debug"
# Search Dockerfile for specific configuration patterns
grep -E "bundle install|RAILS_ENV|ENV|bundle config" Dockerfile
# View entire Dockerfile contents
cat Dockerfile
Bundler Platform Management
# Add platform support to Gemfile.lock
bundle lock --add-platform x86_64-linux
# Install gems (implicit during Docker build)
bundle install
File Modification
# In-place file editing with sed (macOS version)
sed -i '' 's/BUNDLE_WITHOUT="development"/BUNDLE_WITHOUT="development:test"/' Dockerfile
Key Environment Variables in Dockerfile
# Proper production gem exclusion
ENV BUNDLE_WITHOUT="development:test"
# Production Rails environment
ENV RAILS_ENV="production"
# Deployment mode for Bundler
ENV BUNDLE_DEPLOYMENT="1"
These commands formed my investigation toolkit, allowing me to systematically explore file contents, understand configurations, and make targeted fixes. The combination of grep for searching, cat for viewing, sed for editing, and Docker commands for building and tagging created an efficient debugging workflow that I'll continue to use in future containerization challenges.
If you enjoyed this article, you can also find it published on LinkedIn and Medium.