Debugging a Complex Docker API Routing Issue: My Journey Through Rails 8.0.2 and Container Challenges

Debugging a Complex Docker API Routing Issue: My Journey Through Rails 8.0.2 and Container Challenges

The Problem That Started It All

During my work on a full-stack ERP application, I encountered a persistent 404 error that brought my development to a halt. My React frontend couldn't communicate with my Rails API backend, and the error message stared back at me:

GET http://localhost:3002/api/v1/metrics/production 404 (Not Found)

What made this particularly puzzling was that my application ran in a Docker environment with multiple containers. I needed to determine whether the issue stemmed from routing, container configuration, or something else entirely. Rather than making assumptions, I decided to systematically investigate the actual state of my application.

My Systematic Investigation Approach

Step 1: Checking the Route Configuration

My first instinct was to examine what routes were actually defined in my Rails backend. I ran:

cd backend && bin/rails routes | grep -E "(metrics|production)"

The output immediately revealed something unexpected:

ArgumentError: Route `resources :metrics` - :only and :except must include only 
[:index, :create, :new, :show, :update, :destroy, :edit], but also included [:production]

Rails 8.0.2 was rejecting my route definition because :production wasn't a valid RESTful action. This error message was my first real clue.

Step 2: Examining the Routes File

I needed to see the actual configuration causing this error:

cat config/routes.rb

Lines 59-63 revealed the problematic code:

# Metrics routes
resources :metrics, only: [:production] do
  collection do
    get :production
  end
end

The issue became clear immediately. I was trying to use :production as a valid RESTful action in the only: parameter, while also defining it as a collection method. This was both redundant and invalid in Rails routing syntax.

Understanding the Root Cause

My investigation revealed that I had made a fundamental error in Rails routing syntax. I realized the correct approach for creating a custom endpoint like /api/v1/metrics/production should have been either using a simple get route or using resources :metrics with only the collection block, without the invalid only: parameter.

Implementing My Solution

Step 3: Creating a Backup and Fixing the Route

First, I created a backup of my routes file to ensure I could revert if needed:

cp config/routes.rb config/routes.rb.backup

Then I replaced the invalid route configuration with a simple, correct one:

sed -i '62,67c\      # Metrics routes\n      get "metrics/production", to: "metrics#production"' config/routes.rb

This command replaced the complex, invalid route definition with a straightforward GET route that maps /api/v1/metrics/production directly to the metrics#production controller action.

Step 4: Verifying the Route Fix

I verified that Rails could now parse my routes correctly:

bin/rails routes | grep metrics

Success! The output showed:

api_v1_metrics_production GET    /api/v1/metrics/production(.:format)    api/v1/metrics#production

The Docker Container Challenge

Despite fixing the routes, testing the endpoint still returned a 404 error. This led me to a crucial realization: my application was running in Docker containers, not locally. The route changes I made to my local files hadn't been picked up by the Docker containers. This was my second major discovery in this debugging journey.

Step 5: Rebuilding the Docker Container

I needed to rebuild only the backend container with my fixes:

docker-compose -f docker-compose.prod.yml up -d --no-deps --build backend

The --no-deps flag was crucial here—it prevented Docker from trying to recreate other healthy containers (database, redis, etc.) that were already running properly.

After encountering some port conflicts with the old container, I had to take additional steps:

First, stop the old backend container:

docker stop pcvn-erp-backend-prod

Then start the new one:

docker-compose -f docker-compose.prod.yml up -d --no-deps backend

Verification and Success

Finally, I tested the endpoint again:

curl -i http://localhost:3002/api/v1/metrics/production

The response changed dramatically:

  • Before: HTTP/1.1 404 Not Found (route didn't exist)
  • After: HTTP/1.1 401 Unauthorized (route exists, requires authentication)

The 401 response with {"error":"Authorization header missing"} confirmed that my route was now working correctly—it was just properly enforcing JWT authentication as designed. This was exactly the behavior I expected from a protected endpoint.

Reflections and Lessons Learned

This debugging experience reinforced several important principles in my development practice:

  1. Verify actual state over assumptions - I started with the most basic check (route definitions) rather than assuming the configuration was correct.

  2. Systematic investigation pays off - By methodically checking each component, I found the root cause quickly instead of randomly trying fixes.

  3. Environment awareness is crucial - Understanding the difference between local and containerized environments saved me from confusion when my local fixes didn't immediately work.

  4. Minimal, surgical fixes are best - I replaced only the problematic code rather than rewriting entire sections, reducing the risk of introducing new issues.

  5. Verification at each step - Testing after each fix helped me confirm progress and understand exactly what solved the problem.

The issue was ultimately caused by invalid Rails routing syntax that became more strictly enforced in Rails 8.0.2. By combining systematic debugging with an understanding of both Rails routing and Docker container management, I resolved what initially seemed like a complex networking issue with a simple, targeted fix.

This experience reminded me that even seemingly complex problems often have simple root causes. The key is methodical investigation and not jumping to conclusions too quickly. Each error message, each failed attempt, brought me closer to understanding the true nature of the problem. In the end, what started as a frustrating 404 error became a valuable lesson in patient, systematic troubleshooting.


If you enjoyed this article, you can also find it published on LinkedIn and Medium.