Randhana.com
Published on
2 min read

Docker + Node.js + bcrypt: Why It Worked Locally but Failed in Production

Authors
  • avatar
    Name
    Pulathisi Kariyawasam
    LinkedIn
    LinkedIn
Docker Node.js bcrypt issue

While working on a Node.js authentication service, I ran into a strange issue that took some time to understand.
Everything worked perfectly on my local machine, but once I deployed the same code inside Docker, the API started failing without any clear error.

In this article, I want to share what the issue was, how I approached debugging it, what I missed at first, and the solution that finally fixed it.
Hopefully, this will save time for someone facing the same problem.


The Problem I Faced

I had a simple authentication API with a register endpoint.
Locally, the API worked without any issues.

But when I ran the same application inside Docker and tried to call the register endpoint, I got this error:

curl: (52) Empty reply from server

No error message.
No stack trace.
The container just restarted.

At first, this was confusing because the same request worked fine on my local machine.


My Environment Setup

This was my setup at the time:

Local machine

  • Node.js v20.19.4
  • OS: Ubuntu 24.04.3 LTS
  • Framework: Express.js with TypeScript
  • Password hashing: bcrypt

Docker

  • Base image: node:18-alpine
  • Same codebase
  • Same environment variables

This difference looked small, but it was actually the main reason for the issue.


How the Issue Appeared

Local request (working)

curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"TestPass123!"}'

Response:

{
  "success": true,
  "message": "Registration successful"
}

Docker request (failing)

curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"TestPass123!"}'

Response:

curl: (52) Empty reply from server

First Debugging Steps

The container was restarting again and again.

I checked the container status:

docker ps

It showed the service restarting repeatedly.

Health checks were also failing, which confirmed that the application was crashing.


Adding Debug Logs

Since there was no clear error, I added logs step by step inside the register endpoint.

logger.info('Register called');
logger.info('Validation passed');
logger.info('Checking email');
logger.info('About to hash password');

The logs stopped at:

logger.info('About to hash password');

Anything after that never ran.

This told me one important thing:

👉 The crash was happening during password hashing


Finding the Exact Line

Inside my password service, I had this code:

import bcrypt from 'bcrypt';

const hash = await bcrypt.hash(password, saltRounds);

This line worked locally, but inside Docker it caused the app to crash silently.


What I Missed at First

The mistake I made was assuming Docker is just another runtime.

In reality, my environments were different in important ways:

  • Local: Node.js 20 + glibc
  • Docker: Node.js 18 + Alpine Linux (musl)

And this matters a lot for native modules.


Why bcrypt Failed in Docker

bcrypt is not a pure JavaScript library.
It uses native code and needs to be compiled for the system it runs on.

In my case:

  • bcrypt depends on native binaries
  • Alpine Linux uses musl instead of glibc
  • Node version was different (20 locally, 18 in Docker)
  • The native binary did not match the Docker environment

Because of this mismatch, Node crashed instead of throwing a normal error.

That is why I saw "Empty reply from server" instead of a proper exception.


Why It Worked Locally

On my local machine:

  • Node version matched the compiled binary
  • Full build tools were available
  • The OS libraries were compatible

So bcrypt worked without any problem.


The Solution I Chose

After understanding the root cause, I had a few options:

  1. Rebuild bcrypt inside Docker with extra tools
  2. Change the Docker base image
  3. Avoid native modules completely

I chose the simplest and safest option.


Switching to bcryptjs

bcryptjs is a pure JavaScript implementation of bcrypt.
It does not depend on native binaries.

Code Changes

package.json

{
  "dependencies": {
    "bcryptjs": "^2.4.3"
  },
  "devDependencies": {
    "@types/bcryptjs": "^2.4.6"
  }
}

Import change

// Before
import bcrypt from 'bcrypt';

// After
import bcrypt from 'bcryptjs';

Usage stays the same

const hash = await bcrypt.hash(password, saltRounds);
const isValid = await bcrypt.compare(password, hash);

Rebuilding Docker

npm install
docker compose build --no-cache
docker compose up -d

Result After the Fix

After switching to bcryptjs, the Docker container stopped crashing.

The same API call now worked correctly inside Docker.

{
  "success": true,
  "message": "Registration successful"
}

Performance and Security Notes

Featurebcryptbcryptjs
ImplementationNativeJavaScript
Docker friendly
PerformanceFasterSlightly slower
SecurityStrongStrong

For most APIs, the performance difference is not noticeable, but the stability improvement is huge.


Other Possible Solutions (I Did Not Choose)

  1. Install build tools and rebuild bcrypt inside Docker
  2. Use a non-Alpine base image like node:18-bullseye
  3. Fully standardize Node versions across all environments

These options work, but they add more complexity.


Lessons I Learned

Here are the key things I learned from this issue:

  • Docker environments are not the same as local machines
  • Native Node.js modules can fail silently
  • Alpine Linux can cause issues with native libraries
  • Logging step by step helps find silent crashes
  • Pure JavaScript libraries are safer in containers
  • Node version differences matter more than expected

Final Thoughts

This issue took time to debug because there was no clear error message.
But once I understood how native modules work in Docker, the fix was simple.

If your Node.js app works locally but crashes inside Docker, check native dependencies first.

I hope this experience helps someone avoid the same problem.

Thanks for reading 👋

Docker + Node.js + bcrypt: Why It Worked Locally but Failed in Production | Randhana